DBI exercises

library(DBI)
Loading required package: methods
con = dbConnect(RPostgreSQL::PostgreSQL(),
                dbname   = "nycflights13",
                user     = "csor",
                password = "csor")

1 - Load mtcars into the database from R

dbWriteTable(con, "mtcars", mtcars)
[1] TRUE

2 - Calculate summaries with SQL

Average mileage by number of cylinders:

SELECT cyl, am, avg(mpg) FROM mtcars
 GROUP BY cyl, am;

Total weight of the listed cars per manufacturer:

SELECT left("row.names", strpos("row.names", ' ') - 1) AS manufacturer,
       sum(wt)
  FROM mtcars
 GROUP BY manufacturer;

3 - Replicate the results using R

Notice the choice of R string quotes and the escaped single quotes in q2:

q1 <- 'SELECT cyl, am, avg(mpg) FROM mtcars GROUP BY cyl, am;'
dbGetQuery(con, q1)
q2 <- '
SELECT left("row.names", strpos("row.names", \' \') - 1) AS manufacturer,
       sum(wt)
  FROM mtcars
 GROUP BY manufacturer;'
dbGetQuery(con, q2)

4 - Implementation of query_avg

query_avg <- function(con, name, n, m = NULL) {
  fields <- dbListFields(con, name)
  enquote <- function(x) paste0('"', x, '"')
  paste('SELECT',
        if (!is.null(m)) paste0(enquote(fields[m]), ','),
        'avg(', enquote(fields[n]), ') FROM', enquote(name),
        if (!is.null(m)) paste('GROUP BY', enquote(fields[m])), ';')
}

5 - Test of query_avg

Expectation:

  1. q1 should result in an error, not because of invalid syntax, but because the column number 1 is the automatically created row.names column - a string variable, for which an average cannot be calculated. You might expect mpg to be the first column, as it is for the mtcars data frame, but row names do not exist in SQL.
  2. Keeping the above in mind, q2 should return the average disp grouped by vs.
q1 <- query_avg(con, "mtcars", 1)
dbGetQuery(con, q1)
Warning in postgresqlQuickSQL(conn, statement, ...): Could not create
execute: SELECT avg( "row.names" ) FROM "mtcars" ;
NULL
q2 <- query_avg(con, "mtcars", 4, 9)
dbGetQuery(con, q2)

6 - Delete mtcars from the database again

dbRemoveTable(con, "mtcars")
[1] TRUE

tidyverse exercises

library(tidyverse)
flights  <- tbl(con, "flights")
planes   <- tbl(con, "planes")
airlines <- tbl(con, "airlines")
airports <- tbl(con, "airports")

For each of the exercises, both pure SQL and tidyverse solutions are shown.

1 - Average arrival delay for each carrier

SELECT carrier, name, avg(arr_delay)
  FROM flights JOIN airlines USING (carrier)
 GROUP BY carrier, name;
q <- flights %>%
  inner_join(airlines, by = "carrier") %>%
  group_by(carrier, name) %>%
  summarise(mean(arr_delay, na.rm = TRUE))
q
show_query(q)
<SQL>
SELECT "carrier", "name", AVG("arr_delay") AS "mean(arr_delay, na.rm = TRUE)"
FROM (SELECT "TBL_LEFT"."year" AS "year", "TBL_LEFT"."month" AS "month", "TBL_LEFT"."day" AS "day", "TBL_LEFT"."dep_time" AS "dep_time", "TBL_LEFT"."sched_dep_time" AS "sched_dep_time", "TBL_LEFT"."dep_delay" AS "dep_delay", "TBL_LEFT"."arr_time" AS "arr_time", "TBL_LEFT"."sched_arr_time" AS "sched_arr_time", "TBL_LEFT"."arr_delay" AS "arr_delay", "TBL_LEFT"."carrier" AS "carrier", "TBL_LEFT"."flight" AS "flight", "TBL_LEFT"."tailnum" AS "tailnum", "TBL_LEFT"."origin" AS "origin", "TBL_LEFT"."dest" AS "dest", "TBL_LEFT"."air_time" AS "air_time", "TBL_LEFT"."distance" AS "distance", "TBL_LEFT"."hour" AS "hour", "TBL_LEFT"."minute" AS "minute", "TBL_LEFT"."time_hour" AS "time_hour", "TBL_RIGHT"."name" AS "name"
  FROM "flights" AS "TBL_LEFT"
  INNER JOIN "airlines" AS "TBL_RIGHT"
  ON ("TBL_LEFT"."carrier" = "TBL_RIGHT"."carrier")
) "bfewiupicm"
GROUP BY "carrier", "name"

It is clear to see that while you can easily build valid SQL, it’s not necessarily pretty SQL.

2 - Flights without records in planes

Using an anti join subquery, we filter out the records that do have matches and count the remaining.

SELECT carrier, name, count(*)
  FROM flights JOIN airlines USING (carrier)
 WHERE NOT EXISTS (
   SELECT 1 FROM planes
    WHERE tailnum = flights.tailnum
 )
 GROUP BY carrier, name;
q <- flights %>%
  anti_join(planes, by = "tailnum") %>%
  inner_join(airlines, by = "carrier") %>%
  group_by(carrier, name) %>%
  count()
q
show_query(q)
<SQL>
SELECT "carrier", "name", COUNT(*) AS "n"
FROM (SELECT "TBL_LEFT"."year" AS "year", "TBL_LEFT"."month" AS "month", "TBL_LEFT"."day" AS "day", "TBL_LEFT"."dep_time" AS "dep_time", "TBL_LEFT"."sched_dep_time" AS "sched_dep_time", "TBL_LEFT"."dep_delay" AS "dep_delay", "TBL_LEFT"."arr_time" AS "arr_time", "TBL_LEFT"."sched_arr_time" AS "sched_arr_time", "TBL_LEFT"."arr_delay" AS "arr_delay", "TBL_LEFT"."carrier" AS "carrier", "TBL_LEFT"."flight" AS "flight", "TBL_LEFT"."tailnum" AS "tailnum", "TBL_LEFT"."origin" AS "origin", "TBL_LEFT"."dest" AS "dest", "TBL_LEFT"."air_time" AS "air_time", "TBL_LEFT"."distance" AS "distance", "TBL_LEFT"."hour" AS "hour", "TBL_LEFT"."minute" AS "minute", "TBL_LEFT"."time_hour" AS "time_hour", "TBL_RIGHT"."name" AS "name"
  FROM (SELECT * FROM "flights" AS "TBL_LEFT"

WHERE NOT EXISTS (
  SELECT 1 FROM "planes" AS "TBL_RIGHT"
  WHERE ("TBL_LEFT"."tailnum" = "TBL_RIGHT"."tailnum")
)) "TBL_LEFT"
  INNER JOIN "airlines" AS "TBL_RIGHT"
  ON ("TBL_LEFT"."carrier" = "TBL_RIGHT"."carrier")
) "cuieqoklhp"
GROUP BY "carrier", "name"

Still, aside from the added verbosity, the two queries are largely the same.

3 - Number of flights by carrier and destination

SELECT airlines.name AS airline, airports.name AS airport, count(*)
  FROM flights
         JOIN airlines USING (carrier)
         JOIN airports ON (dest = faa)
 GROUP BY airline, airport
 ORDER BY airline, airport;

Sorted by airline and airport for clarity.

q <- flights %>%
  inner_join(airlines, by = "carrier") %>%
  inner_join(airports, by = c("dest" = "faa")) %>%
  rename(airline = name.x,
         airport = name.y) %>%
  group_by(airline, airport) %>%
  count() %>%
  arrange(airline, airport)
q
show_query(q)
<SQL>
SELECT "airline", "airport", COUNT(*) AS "n"
FROM (SELECT "year", "month", "day", "dep_time", "sched_dep_time", "dep_delay", "arr_time", "sched_arr_time", "arr_delay", "carrier", "flight", "tailnum", "origin", "dest", "air_time", "distance", "hour", "minute", "time_hour", "name.x" AS "airline", "name.y" AS "airport", "lat", "lon", "alt", "tz", "dst", "tzone"
FROM (SELECT "TBL_LEFT"."year" AS "year", "TBL_LEFT"."month" AS "month", "TBL_LEFT"."day" AS "day", "TBL_LEFT"."dep_time" AS "dep_time", "TBL_LEFT"."sched_dep_time" AS "sched_dep_time", "TBL_LEFT"."dep_delay" AS "dep_delay", "TBL_LEFT"."arr_time" AS "arr_time", "TBL_LEFT"."sched_arr_time" AS "sched_arr_time", "TBL_LEFT"."arr_delay" AS "arr_delay", "TBL_LEFT"."carrier" AS "carrier", "TBL_LEFT"."flight" AS "flight", "TBL_LEFT"."tailnum" AS "tailnum", "TBL_LEFT"."origin" AS "origin", "TBL_LEFT"."dest" AS "dest", "TBL_LEFT"."air_time" AS "air_time", "TBL_LEFT"."distance" AS "distance", "TBL_LEFT"."hour" AS "hour", "TBL_LEFT"."minute" AS "minute", "TBL_LEFT"."time_hour" AS "time_hour", "TBL_LEFT"."name" AS "name.x", "TBL_RIGHT"."name" AS "name.y", "TBL_RIGHT"."lat" AS "lat", "TBL_RIGHT"."lon" AS "lon", "TBL_RIGHT"."alt" AS "alt", "TBL_RIGHT"."tz" AS "tz", "TBL_RIGHT"."dst" AS "dst", "TBL_RIGHT"."tzone" AS "tzone"
  FROM (SELECT "TBL_LEFT"."year" AS "year", "TBL_LEFT"."month" AS "month", "TBL_LEFT"."day" AS "day", "TBL_LEFT"."dep_time" AS "dep_time", "TBL_LEFT"."sched_dep_time" AS "sched_dep_time", "TBL_LEFT"."dep_delay" AS "dep_delay", "TBL_LEFT"."arr_time" AS "arr_time", "TBL_LEFT"."sched_arr_time" AS "sched_arr_time", "TBL_LEFT"."arr_delay" AS "arr_delay", "TBL_LEFT"."carrier" AS "carrier", "TBL_LEFT"."flight" AS "flight", "TBL_LEFT"."tailnum" AS "tailnum", "TBL_LEFT"."origin" AS "origin", "TBL_LEFT"."dest" AS "dest", "TBL_LEFT"."air_time" AS "air_time", "TBL_LEFT"."distance" AS "distance", "TBL_LEFT"."hour" AS "hour", "TBL_LEFT"."minute" AS "minute", "TBL_LEFT"."time_hour" AS "time_hour", "TBL_RIGHT"."name" AS "name"
  FROM "flights" AS "TBL_LEFT"
  INNER JOIN "airlines" AS "TBL_RIGHT"
  ON ("TBL_LEFT"."carrier" = "TBL_RIGHT"."carrier")
) "TBL_LEFT"
  INNER JOIN "airports" AS "TBL_RIGHT"
  ON ("TBL_LEFT"."dest" = "TBL_RIGHT"."faa")
) "tabfxaztkf") "gcbgkfrtcs"
GROUP BY "airline", "airport"
ORDER BY "airline", "airport"
LS0tCnRpdGxlOiAiTGVjdHVyZSAwMyBleGVyY2lzZSBzb2x1dGlvbnMiCmF1dGhvcjogIkphbnVzIFZhbGJlcmctTWFkc2VuIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiBjb3NtbwogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIpCmBgYAoKIyMgREJJIGV4ZXJjaXNlcwoKYGBge3IgY29ubmVjdGlvbn0KbGlicmFyeShEQkkpCmNvbiA9IGRiQ29ubmVjdChSUG9zdGdyZVNRTDo6UG9zdGdyZVNRTCgpLAogICAgICAgICAgICAgICAgZGJuYW1lICAgPSAibnljZmxpZ2h0czEzIiwKICAgICAgICAgICAgICAgIHVzZXIgICAgID0gImNzb3IiLAogICAgICAgICAgICAgICAgcGFzc3dvcmQgPSAiY3NvciIpCmBgYAoKYGBge3IgY29uLXNldCwgaW5jbHVkZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoY29ubmVjdGlvbiA9ICJjb24iKQpgYGAKCgojIyMgMSAtIExvYWQgYG10Y2Fyc2AgaW50byB0aGUgZGF0YWJhc2UgZnJvbSBSCgpgYGB7ciBleDF9CmRiV3JpdGVUYWJsZShjb24sICJtdGNhcnMiLCBtdGNhcnMpCmBgYAoKCiMjIyAyIC0gQ2FsY3VsYXRlIHN1bW1hcmllcyB3aXRoIFNRTAoKQXZlcmFnZSBtaWxlYWdlIGJ5IG51bWJlciBvZiBjeWxpbmRlcnM6CgpgYGB7c3FsIGV4Mi0xfQpTRUxFQ1QgY3lsLCBhbSwgYXZnKG1wZykgRlJPTSBtdGNhcnMKIEdST1VQIEJZIGN5bCwgYW07CmBgYAoKVG90YWwgd2VpZ2h0IG9mIHRoZSBsaXN0ZWQgY2FycyBwZXIgbWFudWZhY3R1cmVyOgoKYGBge3NxbCBleDItMn0KU0VMRUNUIGxlZnQoInJvdy5uYW1lcyIsIHN0cnBvcygicm93Lm5hbWVzIiwgJyAnKSAtIDEpIEFTIG1hbnVmYWN0dXJlciwKICAgICAgIHN1bSh3dCkKICBGUk9NIG10Y2FycwogR1JPVVAgQlkgbWFudWZhY3R1cmVyOwpgYGAKCgojIyMgMyAtIFJlcGxpY2F0ZSB0aGUgcmVzdWx0cyB1c2luZyBSCgpOb3RpY2UgdGhlIGNob2ljZSBvZiBSIHN0cmluZyBxdW90ZXMgYW5kIHRoZSBlc2NhcGVkIHNpbmdsZSBxdW90ZXMgaW4gYHEyYDoKCmBgYHtyIGV4M30KcTEgPC0gJ1NFTEVDVCBjeWwsIGFtLCBhdmcobXBnKSBGUk9NIG10Y2FycyBHUk9VUCBCWSBjeWwsIGFtOycKZGJHZXRRdWVyeShjb24sIHExKQpxMiA8LSAnClNFTEVDVCBsZWZ0KCJyb3cubmFtZXMiLCBzdHJwb3MoInJvdy5uYW1lcyIsIFwnIFwnKSAtIDEpIEFTIG1hbnVmYWN0dXJlciwKICAgICAgIHN1bSh3dCkKICBGUk9NIG10Y2FycwogR1JPVVAgQlkgbWFudWZhY3R1cmVyOycKZGJHZXRRdWVyeShjb24sIHEyKQpgYGAKCgojIyMgNCAtIEltcGxlbWVudGF0aW9uIG9mIGBxdWVyeV9hdmdgCgpgYGB7ciBleDR9CnF1ZXJ5X2F2ZyA8LSBmdW5jdGlvbihjb24sIG5hbWUsIG4sIG0gPSBOVUxMKSB7CiAgZmllbGRzIDwtIGRiTGlzdEZpZWxkcyhjb24sIG5hbWUpCiAgZW5xdW90ZSA8LSBmdW5jdGlvbih4KSBwYXN0ZTAoJyInLCB4LCAnIicpCiAgcGFzdGUoJ1NFTEVDVCcsCiAgICAgICAgaWYgKCFpcy5udWxsKG0pKSBwYXN0ZTAoZW5xdW90ZShmaWVsZHNbbV0pLCAnLCcpLAogICAgICAgICdhdmcoJywgZW5xdW90ZShmaWVsZHNbbl0pLCAnKSBGUk9NJywgZW5xdW90ZShuYW1lKSwKICAgICAgICBpZiAoIWlzLm51bGwobSkpIHBhc3RlKCdHUk9VUCBCWScsIGVucXVvdGUoZmllbGRzW21dKSksICc7JykKfQpgYGAKCgojIyMgNSAtIFRlc3Qgb2YgYHF1ZXJ5X2F2Z2AKCkV4cGVjdGF0aW9uOgoKMS4gYHExYCBzaG91bGQgcmVzdWx0IGluIGFuIGVycm9yLCBub3QgYmVjYXVzZSBvZiBpbnZhbGlkIHN5bnRheCwgYnV0IGJlY2F1c2UgdGhlIGNvbHVtbiBudW1iZXIgMSBpcyB0aGUgYXV0b21hdGljYWxseSBjcmVhdGVkIGByb3cubmFtZXNgIGNvbHVtbiAtIGEgc3RyaW5nIHZhcmlhYmxlLCBmb3Igd2hpY2ggYW4gYXZlcmFnZSBjYW5ub3QgYmUgY2FsY3VsYXRlZC4gWW91IG1pZ2h0IGV4cGVjdCBgbXBnYCB0byBiZSB0aGUgZmlyc3QgY29sdW1uLCBhcyBpdCBpcyBmb3IgdGhlIGBtdGNhcnNgIGRhdGEgZnJhbWUsIGJ1dCByb3cgbmFtZXMgZG8gbm90IGV4aXN0IGluIFNRTC4KMi4gS2VlcGluZyB0aGUgYWJvdmUgaW4gbWluZCwgYHEyYCBzaG91bGQgcmV0dXJuIHRoZSBhdmVyYWdlIGBkaXNwYCBncm91cGVkIGJ5IGB2c2AuCgpgYGB7ciBleDUsIGVycm9yID0gVFJVRX0KcTEgPC0gcXVlcnlfYXZnKGNvbiwgIm10Y2FycyIsIDEpCmRiR2V0UXVlcnkoY29uLCBxMSkKcTIgPC0gcXVlcnlfYXZnKGNvbiwgIm10Y2FycyIsIDQsIDkpCmRiR2V0UXVlcnkoY29uLCBxMikKYGBgCgoKIyMjIDYgLSBEZWxldGUgYG10Y2Fyc2AgZnJvbSB0aGUgZGF0YWJhc2UgYWdhaW4KCmBgYHtyfQpkYlJlbW92ZVRhYmxlKGNvbiwgIm10Y2FycyIpCmBgYAoKPCEtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLT4KCgojIyB0aWR5dmVyc2UgZXhlcmNpc2VzCgpgYGB7ciB0aWR5LXNldHVwLCBtZXNzYWdlID0gRkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpmbGlnaHRzICA8LSB0YmwoY29uLCAiZmxpZ2h0cyIpCnBsYW5lcyAgIDwtIHRibChjb24sICJwbGFuZXMiKQphaXJsaW5lcyA8LSB0YmwoY29uLCAiYWlybGluZXMiKQphaXJwb3J0cyA8LSB0YmwoY29uLCAiYWlycG9ydHMiKQpgYGAKCgpGb3IgZWFjaCBvZiB0aGUgZXhlcmNpc2VzLCBib3RoIHB1cmUgU1FMIGFuZCB0aWR5dmVyc2Ugc29sdXRpb25zIGFyZSBzaG93bi4KCgojIyMgMSAtIEF2ZXJhZ2UgYXJyaXZhbCBkZWxheSBmb3IgZWFjaCBjYXJyaWVyCgpgYGB7c3FsfQpTRUxFQ1QgY2FycmllciwgbmFtZSwgYXZnKGFycl9kZWxheSkKICBGUk9NIGZsaWdodHMgSk9JTiBhaXJsaW5lcyBVU0lORyAoY2FycmllcikKIEdST1VQIEJZIGNhcnJpZXIsIG5hbWU7CmBgYAoKYGBge3IgZXgtdGlkeTF9CnEgPC0gZmxpZ2h0cyAlPiUKICBpbm5lcl9qb2luKGFpcmxpbmVzLCBieSA9ICJjYXJyaWVyIikgJT4lCiAgZ3JvdXBfYnkoY2FycmllciwgbmFtZSkgJT4lCiAgc3VtbWFyaXNlKG1lYW4oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpKQpxCnNob3dfcXVlcnkocSkKYGBgCgpJdCBpcyBjbGVhciB0byBzZWUgdGhhdCB3aGlsZSB5b3UgY2FuIGVhc2lseSBidWlsZCB2YWxpZCBTUUwsIGl0J3Mgbm90IG5lY2Vzc2FyaWx5IHByZXR0eSBTUUwuCgoKIyMjIDIgLSBGbGlnaHRzIHdpdGhvdXQgcmVjb3JkcyBpbiBgcGxhbmVzYAoKVXNpbmcgYW4gX2FudGkgam9pbl8gc3VicXVlcnksIHdlIGZpbHRlciBvdXQgdGhlIHJlY29yZHMgdGhhdCBkbyBoYXZlIG1hdGNoZXMgYW5kIGNvdW50IHRoZSByZW1haW5pbmcuCgpgYGB7c3FsfQpTRUxFQ1QgY2FycmllciwgbmFtZSwgY291bnQoKikKICBGUk9NIGZsaWdodHMgSk9JTiBhaXJsaW5lcyBVU0lORyAoY2FycmllcikKIFdIRVJFIE5PVCBFWElTVFMgKAogICBTRUxFQ1QgMSBGUk9NIHBsYW5lcwogICAgV0hFUkUgdGFpbG51bSA9IGZsaWdodHMudGFpbG51bQogKQogR1JPVVAgQlkgY2FycmllciwgbmFtZTsKYGBgCgpgYGB7ciBleC10aWR5Mn0KcSA8LSBmbGlnaHRzICU+JQogIGFudGlfam9pbihwbGFuZXMsIGJ5ID0gInRhaWxudW0iKSAlPiUKICBpbm5lcl9qb2luKGFpcmxpbmVzLCBieSA9ICJjYXJyaWVyIikgJT4lCiAgZ3JvdXBfYnkoY2FycmllciwgbmFtZSkgJT4lCiAgY291bnQoKQpxCnNob3dfcXVlcnkocSkKYGBgCgpTdGlsbCwgYXNpZGUgZnJvbSB0aGUgYWRkZWQgdmVyYm9zaXR5LCB0aGUgdHdvIHF1ZXJpZXMgYXJlIGxhcmdlbHkgdGhlIHNhbWUuCgoKIyMjIDMgLSBOdW1iZXIgb2YgZmxpZ2h0cyBieSBjYXJyaWVyIGFuZCBkZXN0aW5hdGlvbgoKYGBge3NxbH0KU0VMRUNUIGFpcmxpbmVzLm5hbWUgQVMgYWlybGluZSwgYWlycG9ydHMubmFtZSBBUyBhaXJwb3J0LCBjb3VudCgqKQogIEZST00gZmxpZ2h0cwogICAgICAgICBKT0lOIGFpcmxpbmVzIFVTSU5HIChjYXJyaWVyKQogICAgICAgICBKT0lOIGFpcnBvcnRzIE9OIChkZXN0ID0gZmFhKQogR1JPVVAgQlkgYWlybGluZSwgYWlycG9ydAogT1JERVIgQlkgYWlybGluZSwgYWlycG9ydDsKYGBgCgpTb3J0ZWQgYnkgYWlybGluZSBhbmQgYWlycG9ydCBmb3IgY2xhcml0eS4KCmBgYHtyIGV4LXRpZHkzfQpxIDwtIGZsaWdodHMgJT4lCiAgaW5uZXJfam9pbihhaXJsaW5lcywgYnkgPSAiY2FycmllciIpICU+JQogIGlubmVyX2pvaW4oYWlycG9ydHMsIGJ5ID0gYygiZGVzdCIgPSAiZmFhIikpICU+JQogIHJlbmFtZShhaXJsaW5lID0gbmFtZS54LAogICAgICAgICBhaXJwb3J0ID0gbmFtZS55KSAlPiUKICBncm91cF9ieShhaXJsaW5lLCBhaXJwb3J0KSAlPiUKICBjb3VudCgpICU+JQogIGFycmFuZ2UoYWlybGluZSwgYWlycG9ydCkKcQpzaG93X3F1ZXJ5KHEpCmBgYAo=