Introduction
SQL is a universal query language. Its use has expanded to any tabular data and not just relational databases. sqldf is a package that allows data frames to be queried with SQL as if they were tables in a database. It allows a programmer or data analyst to use SQL to access, process, search, and aggregate data in data frames.
Many queries, while generally doable in Base R or with tidyverse, are often simpler with a SQL query – albeit a bit slower, but that reduction in performance is often not perceptible. The sqldf package actually loads the data frame into an in-memory SQLite database.
sqldf is primarily used to:
- summarize of data in data frames
- harmonize data access via SQL for all tabular data
- import data from CSV files
- process parts of a CSV
- load data into databases
- learn SQL
Aside from sqldf, the tidyverse package also contains numerous functions for processing data frames in a SQL-like manner.
sqldf should only be used for queries (SQL SELECT) and not for updates (SQL INSERT, UPDATE, DELETE) as that actually modifies a copy of the data frame.
Simple Example
In memory data frames can be treated like tables in a relational database. You can simply use the name of a data frame in a sqldf SQL query. Note that each sqldf query returns a data frame – just like any SQL query returns a table.
rs <- sqldf("select Species, count(*) as num
from iris
group by Species")
head(rs, 3)
## Species num
## 1 setosa 50
## 2 versicolor 50
## 3 virginica 50
The resulting data frame can be further processed in sqldf or with R.
## [1] 50
Parameters in sqldf Queries
You can use R variables as parameters for sqldf queries but you need to build a single string using paste0()
.
Note that in the example below, the Sepal.Length column contains a ‘dot’, so it must be enclosed in backticks. If not, then you will get a “Column not found” error.
n <- 5
rs <- sqldf(paste0("select count(Species) as 'NumSpecies'
from iris
where `Sepal.Length` < ", n))
print(rs$NumSpecies)
## [1] 22
Note that constructing queries by pasting values into a single string is generally bad practice and can lead to SQL Injection Attacks and therefore should be avoided. However, for data query tasks where the parameters are not user provided, it is acceptable to do.
Querying CSV Data
We will demonstrate the use of sqldf by querying the data in a large CSV file. The file is first loaded into a data frame and then treated as if it were a table in a database even though the data resides fully in memory. Of course, this is not scalable to arbitrarily large data files due to memory constraints.
txns <- read.csv("customertxndata.csv",
header = F,
col.names = c("visits","numtxns",
"os","gender",
"total"))
The CSV file contains data on visits to an e-commerce site.
## visits numtxns os gender total
## 1 NumVisits NumOrders OS Gender Total
## 2 7 0 Android Male 0
## 3 20 1 iOS <NA> 576.866774966349
## 4 22 1 iOS Female 850
sqldf executes a SQL query against an in-memory data frame and returns the query result as a data frame. The data frame can be captured and then processed further with sqldf or R.
library(sqldf)
sqlq <- "select gender, sum(total) as total
from `txns`
group by gender"
df <- sqldf(sqlq)
head(df, 3)
## gender total
## 1 <NA> 2522332.10607994
## 2 Female 2790500
## 3 Gender 0
# imagine you need to ask user for OS,
# perhaps by choosing from a menu
theOS <- "Android"
sqlq <- paste0("select gender, sum(total) as total
from `txns` where os='",theOS, "'
group by gender")
df <- sqldf(sqlq)
head(df, 4)
## gender total
## 1 <NA> 986845.829137997
## 2 Female 308425
## 3 Male 2804647.56080426
Joins in sqldf
sqldf is able to combine the data in multiple (related) data frames or CSV files through a SQL join. All of the usual join mechanisms of SQL are available: inner join, outer join, natural join, and theta join.
df.txn <- read.csv("pharmaSalesTxn.csv")
df.rep <- read.csv("pharmaReps.csv")
## txnID date cust prod qty amount country repID
## 1 1001 9/18/2020 Proin Dolor Institut Xinoprozen 800 2256 Germany 221
## 2 1002 8/10/2020 Proin Dolor Institut Gerantrazeophem 1200 2244 Germany 221
## 3 1003 3/14/2020 Varius Plc Colophrazen 600 738 Brazil 887
## repID repFN repLN repTR
## 1 100 Helmut Schwab EMEA
## 2 887 Walison da Silva South America
## 3 332 Lynette McRowe East
rs <- sqldf("SELECT r.repLN AS LastName,
printf('$%,d', sum(t.amount)) AS 'Total Sold'
FROM `df.rep` AS r JOIN `df.txn` AS t
ON (r.repID = t.repID)
GROUP BY t.repID")
## LastName Total Sold
## 1 Schwab $387,073
## 2 Kappoorthy $609,647
## 3 Sixt $281,456
## 4 McRowe $479,868
## 5 da Silva $993,434
Applying SQL Directly to a CSV
sqldf contains the function read.csv.sql()
which reads a file into R after applying a SQL statement. Only those rows that meet the SQL query criteria are processed by R. This can be helpful when processing large CSV files. The implied table name is file.
sqlQuery <- "SELECT Gender, sum(Total) AS 'SumTotal'
FROM file
WHERE Gender <> 'NA'
GROUP BY Gender"
rs <- read.csv.sql("customertxndata.csv",
sql = sqlQuery,
header = T)
head(rs,3)
## Gender SumTotal
## 1 "Female" 2790500
## 2 "Male" 5059692
Note that the CSV file is presumed to have a header row. The header column names are used as column names for the virtual table. If no header row is in the CSV, then the parameter header = F must be specified and columns names are assigned as V1, V2, and so forth.
The function attempts to guess the data types from the values but that can be changed via the colClasses parameter. Column names and types (using SQL data types) can be done via field.types. This can significantly improve read performance as R does not have to derive data types via inspection of values.
Note that quotes are considered part of a field. So, if the CSV file contains “Female”, then the value is read into R and used by SQL as “Female” and not simply the value Female.
sqldf vs R Functions
R functions for querying dataframes, such as which()
, are generally preferable as they do not require the dataframe to be copied to a temporary database in order to apply a SQL statement. Use sqldf for grouping or aggregation that are difficult to do in R. By the waym tidyverse offers a set of functions for grouping and other selection that are similar to what can be done with SQL in sqldf. So, as is often the case, there’s many ways to do accomplish the same goal in R.
Potential Issues
A potential issue with sqldf can occur when you connect to MySQL or a non-SQLite database as sqldf attempts to use your existing database connection as a backing store for its data; this will often not work due to security constraints. So, you need to add the R code options(sqldf.driver = ‘SQLite’)
which forces sqldf to use SQLite as its backing store where it will create an in-memory database.
It is not uncommon for R to freeze or even crash when a SQL query for sqldf is not properly formatted.
Updating Dataframes via sqldf
Don’t do that… sqldf works on a table in the backing store rather than the actual dataframe, so any updates would happen to that temporary table and not the dataframe. So, no INSERT, UPDATE, DELETE, or ALTER TABLE with sqldf. Use standard R functions for that.
Worked Example
library(sqldf)
url <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/customers.csv"
df <- read.csv(url, header = T, stringsAsFactors = F)
sql <- paste0 (
"SELECT country, count(*) as `num` ",
" FROM `df` ",
" GROUP BY country ",
" ORDER BY `num` DESC ",
" LIMIT 1;"
)
sqldf(sql)
## country num
## 1 Germany 457
or, alternatively,
library(sqldf)
url <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/customers.csv"
df <- read.csv.sql(url, header = T, stringsAsFactors = F)
sql <- paste0 (
"SELECT country, count(*) as `num` ",
" FROM `df` ",
" GROUP BY country ",
" ORDER BY `num` DESC ",
" LIMIT 1;"
)
read.csv.sql(file = url,
sql = sql)
## country num
## 1 Germany 457
Errata
None collected yet. Let us know.
LS0tCnRpdGxlOiAiUXVlcnlpbmcgRGF0YSBGcmFtZXMgaW4gUiB3aXRoIDxpPnNxbGRmPC9pPiIKcGFyYW1zOgogIGNhdGVnb3J5OiA2CiAgbnVtYmVyOiAzMzAKICB0aW1lOiA0NQogIGxldmVsOiBiZWdpbm5lcgogIHRhZ3M6ICJyLFNRTCxzcWxkZixkYXRhYmFzZSxzcWxpdGUiCiAgZGVzY3JpcHRpb246ICJTaW1pbGFyIHRvIG90aGVyIGxhbmd1YWdlcywgUiBhbGxvd3MgZGF0YSBpbiBkYXRhIGZyYW1lcwogICAgICAgICAgICAgICAgKGZyb20gQ1NWLCBYTUwsIG9yIG90aGVyIHNvdXJjZXMpIHRvIGJlIHRyZWF0ZWQgYXMgaWYgdGhleQogICAgICAgICAgICAgICAgd2VyZSB0YWJsZXMgaW4gYSByZWxhdGlvbmFsIGRhdGFiYXNlIGFuZCBxdWVyeSB0aGUgZGF0YSBmcmFtZXMKICAgICAgICAgICAgICAgIHVzaW5nIFNRTC4gRGVtb25zdHJhdGVzIGhvdyB0byBmaW5kIGRhdGEgaW4gZGF0YSBmcmFtZXMsIGNvbWJpbmUgZGF0YQogICAgICAgICAgICAgICAgZnJvbSBtdWx0aXBsZSBkYXRhIGZyYW1lcywgYW5kIGFnZ3JlZ2F0ZSBkYXRhIGluIGRhdGEgZnJhbWVzCiAgICAgICAgICAgICAgICB1c2luZyBTUUwgdGhyb3VnaCB0aGUgc3FsZGYgcGFja2FnZS4iCmRhdGU6ICI8c21hbGw+YHIgU3lzLkRhdGUoKWA8L3NtYWxsPiIKYXV0aG9yOiAiPHNtYWxsPk1hcnRpbiBTY2hlZGxiYXVlcjwvc21hbGw+IgplbWFpbDogIm0uc2NoZWRsYmF1ZXJAbmV1LmVkdSIKYWZmaWxpdGF0aW9uOiAiTm9ydGhlYXN0ZXJuIFVuaXZlcnNpdHkiCm91dHB1dDogCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgaGlnaGxpZ2h0OiB0YW5nbwotLS0KCi0tLQp0aXRsZTogIjxzbWFsbD5gciBwYXJhbXMkY2F0ZWdvcnlgLmByIHBhcmFtcyRudW1iZXJgPC9zbWFsbD48YnIvPjxzcGFuIHN0eWxlPSdjb2xvcjogIzJFNDA1MzsgZm9udC1zaXplOiAwLjllbSc+YHIgcm1hcmtkb3duOjptZXRhZGF0YSR0aXRsZWA8L3NwYW4+IgotLS0KCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19pbnNlcnQyREIuUicpKSwgaW5jbHVkZSA9IEZBTFNFfQpgYGAKCiMjIEludHJvZHVjdGlvbgoKU1FMIGlzIGEgdW5pdmVyc2FsIHF1ZXJ5IGxhbmd1YWdlLiBJdHMgdXNlIGhhcyBleHBhbmRlZCB0byBhbnkgdGFidWxhciBkYXRhIGFuZCBub3QganVzdCByZWxhdGlvbmFsIGRhdGFiYXNlcy4gKipzcWxkZioqIGlzIGEgcGFja2FnZSB0aGF0IGFsbG93cyBkYXRhIGZyYW1lcyB0byBiZSBxdWVyaWVkIHdpdGggU1FMIGFzIGlmIHRoZXkgd2VyZSB0YWJsZXMgaW4gYSBkYXRhYmFzZS4gSXQgYWxsb3dzIGEgcHJvZ3JhbW1lciBvciBkYXRhIGFuYWx5c3QgdG8gdXNlIFNRTCB0byBhY2Nlc3MsIHByb2Nlc3MsIHNlYXJjaCwgYW5kIGFnZ3JlZ2F0ZSBkYXRhIGluIGRhdGEgZnJhbWVzLgoKTWFueSBxdWVyaWVzLCB3aGlsZSBnZW5lcmFsbHkgZG9hYmxlIGluIEJhc2UgUiBvciB3aXRoICoqdGlkeXZlcnNlKiosIGFyZSBvZnRlbiBzaW1wbGVyIHdpdGggYSBTUUwgcXVlcnkgLS0gYWxiZWl0IGEgYml0IHNsb3dlciwgYnV0IHRoYXQgcmVkdWN0aW9uIGluIHBlcmZvcm1hbmNlIGlzIG9mdGVuIG5vdCBwZXJjZXB0aWJsZS4gVGhlICoqc3FsZGYqKiBwYWNrYWdlIGFjdHVhbGx5IGxvYWRzIHRoZSBkYXRhIGZyYW1lIGludG8gYW4gaW4tbWVtb3J5IFNRTGl0ZSBkYXRhYmFzZS4KCioqc3FsZGYqKiBpcyBwcmltYXJpbHkgdXNlZCB0bzoKCi0gICBzdW1tYXJpemUgb2YgZGF0YSBpbiBkYXRhIGZyYW1lcwotICAgaGFybW9uaXplIGRhdGEgYWNjZXNzIHZpYSBTUUwgZm9yIGFsbCB0YWJ1bGFyIGRhdGEKLSAgIGltcG9ydCBkYXRhIGZyb20gQ1NWIGZpbGVzCi0gICBwcm9jZXNzIHBhcnRzIG9mIGEgQ1NWCi0gICBsb2FkIGRhdGEgaW50byBkYXRhYmFzZXMKLSAgIGxlYXJuIFNRTAoKQXNpZGUgZnJvbSAqKnNxbGRmKiosIHRoZSAqKnRpZHl2ZXJzZSoqIHBhY2thZ2UgYWxzbyBjb250YWlucyBudW1lcm91cyBmdW5jdGlvbnMgZm9yIHByb2Nlc3NpbmcgZGF0YSBmcmFtZXMgaW4gYSBTUUwtbGlrZSBtYW5uZXIuCgo+ICoqc3FsZGYqKiBzaG91bGQgb25seSBiZSB1c2VkIGZvciBxdWVyaWVzIChTUUwgU0VMRUNUKSBhbmQgbm90IGZvciB1cGRhdGVzIChTUUwgSU5TRVJULCBVUERBVEUsIERFTEVURSkgYXMgdGhhdCBhY3R1YWxseSBtb2RpZmllcyBhIGNvcHkgb2YgdGhlIGRhdGEgZnJhbWUuCgojIyBTaW1wbGUgRXhhbXBsZQoKSW4gbWVtb3J5IGRhdGEgZnJhbWVzIGNhbiBiZSB0cmVhdGVkIGxpa2UgdGFibGVzIGluIGEgcmVsYXRpb25hbCBkYXRhYmFzZS4gWW91IGNhbiBzaW1wbHkgdXNlIHRoZSBuYW1lIG9mIGEgZGF0YSBmcmFtZSBpbiBhICoqc3FsZGYqKiBTUUwgcXVlcnkuIE5vdGUgdGhhdCBlYWNoICoqc3FsZGYqKiBxdWVyeSByZXR1cm5zIGEgZGF0YSBmcmFtZSAtLSBqdXN0IGxpa2UgYW55IFNRTCBxdWVyeSByZXR1cm5zIGEgdGFibGUuCgpgYGB7ciBsb2FkTGlicywgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShzcWxkZikKYGBgCgpgYGB7ciBsb2FkTGliczIsIHdhcm5pbmc9RkFMU0UsIGVjaG89VFJVRSwgZXZhbD1GQUxTRX0KbGlicmFyeShzcWxkZikKYGBgCgpgYGB7ciBkZW1vU1FMREYsIHdhcm5pbmc9Rn0KcnMgPC0gc3FsZGYoInNlbGVjdCBTcGVjaWVzLCBjb3VudCgqKSBhcyBudW0gCiAgICAgICAgICAgICAgIGZyb20gaXJpcyAKICAgICAgICAgICAgICBncm91cCBieSBTcGVjaWVzIikKaGVhZChycywgMykKYGBgCgpUaGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUgY2FuIGJlIGZ1cnRoZXIgcHJvY2Vzc2VkIGluICoqc3FsZGYqKiBvciB3aXRoIFIuCgpgYGB7cn0KbWVhbihycyRudW0pCmBgYAoKIyMgUGFyYW1ldGVycyBpbiAqKnNxbGRmKiogUXVlcmllcwoKWW91IGNhbiB1c2UgUiB2YXJpYWJsZXMgYXMgcGFyYW1ldGVycyBmb3IgKipzcWxkZioqIHF1ZXJpZXMgYnV0IHlvdSBuZWVkIHRvIGJ1aWxkIGEgc2luZ2xlIHN0cmluZyB1c2luZyA8Y29kZT5wYXN0ZTAoKTwvY29kZT4uCgpOb3RlIHRoYXQgaW4gdGhlIGV4YW1wbGUgYmVsb3csIHRoZSAqU2VwYWwuTGVuZ3RoKiBjb2x1bW4gY29udGFpbnMgYSAnZG90Jywgc28gaXQgbXVzdCBiZSBlbmNsb3NlZCBpbiBiYWNrdGlja3MuIElmIG5vdCwgdGhlbiB5b3Ugd2lsbCBnZXQgYSAiQ29sdW1uIG5vdCBmb3VuZCIgZXJyb3IuCgpgYGB7ciBzcWxkZldpdGhQYXJtc30KbiA8LSA1CnJzIDwtIHNxbGRmKHBhc3RlMCgic2VsZWN0IGNvdW50KFNwZWNpZXMpIGFzICdOdW1TcGVjaWVzJyAKICAgICAgICAgICAgICAgICAgICAgIGZyb20gaXJpcyAKICAgICAgICAgICAgICAgICAgICAgd2hlcmUgYFNlcGFsLkxlbmd0aGAgPCAiLCBuKSkKCnByaW50KHJzJE51bVNwZWNpZXMpCmBgYAoKTm90ZSB0aGF0IGNvbnN0cnVjdGluZyBxdWVyaWVzIGJ5IHBhc3RpbmcgdmFsdWVzIGludG8gYSBzaW5nbGUgc3RyaW5nIGlzIGdlbmVyYWxseSBiYWQgcHJhY3RpY2UgYW5kIGNhbiBsZWFkIHRvIFNRTCBJbmplY3Rpb24gQXR0YWNrcyBhbmQgdGhlcmVmb3JlIHNob3VsZCBiZSBhdm9pZGVkLiBIb3dldmVyLCBmb3IgZGF0YSBxdWVyeSB0YXNrcyB3aGVyZSB0aGUgcGFyYW1ldGVycyBhcmUgbm90IHVzZXIgcHJvdmlkZWQsIGl0IGlzIGFjY2VwdGFibGUgdG8gZG8uCgojIyBRdWVyeWluZyBDU1YgRGF0YQoKV2Ugd2lsbCBkZW1vbnN0cmF0ZSB0aGUgdXNlIG9mICoqc3FsZGYqKiBieSBxdWVyeWluZyB0aGUgZGF0YSBpbiBhIGxhcmdlIENTViBmaWxlLiBUaGUgZmlsZSBpcyBmaXJzdCBsb2FkZWQgaW50byBhIGRhdGEgZnJhbWUgYW5kIHRoZW4gdHJlYXRlZCBhcyBpZiBpdCB3ZXJlIGEgdGFibGUgaW4gYSBkYXRhYmFzZSBldmVuIHRob3VnaCB0aGUgZGF0YSByZXNpZGVzIGZ1bGx5IGluIG1lbW9yeS4gT2YgY291cnNlLCB0aGlzIGlzIG5vdCBzY2FsYWJsZSB0byBhcmJpdHJhcmlseSBsYXJnZSBkYXRhIGZpbGVzIGR1ZSB0byBtZW1vcnkgY29uc3RyYWludHMuCgpgYGB7ciBsb2FkQ1NWfQp0eG5zIDwtIHJlYWQuY3N2KCJjdXN0b21lcnR4bmRhdGEuY3N2IiwgCiAgICAgICAgICAgICAgIGhlYWRlciA9IEYsIAogICAgICAgICAgICAgICBjb2wubmFtZXMgPSBjKCJ2aXNpdHMiLCJudW10eG5zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAib3MiLCJnZW5kZXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0b3RhbCIpKQpgYGAKClRoZSBDU1YgZmlsZSBjb250YWlucyBkYXRhIG9uIHZpc2l0cyB0byBhbiBlLWNvbW1lcmNlIHNpdGUuCgpgYGB7ciwgZXZhbD1UfQpoZWFkKHR4bnMsIDQpCmBgYAoKKipzcWxkZioqIGV4ZWN1dGVzIGEgU1FMIHF1ZXJ5IGFnYWluc3QgYW4gaW4tbWVtb3J5IGRhdGEgZnJhbWUgYW5kIHJldHVybnMgdGhlIHF1ZXJ5IHJlc3VsdCBhcyBhIGRhdGEgZnJhbWUuIFRoZSBkYXRhIGZyYW1lIGNhbiBiZSBjYXB0dXJlZCBhbmQgdGhlbiBwcm9jZXNzZWQgZnVydGhlciB3aXRoICoqc3FsZGYqKiBvciBSLgoKYGBge3Igd2FybmluZz1GfQpsaWJyYXJ5KHNxbGRmKQoKc3FscSA8LSAic2VsZWN0IGdlbmRlciwgc3VtKHRvdGFsKSBhcyB0b3RhbCAKICAgICAgICAgICBmcm9tIGB0eG5zYCAKICAgICAgICAgIGdyb3VwIGJ5IGdlbmRlciIKCmRmIDwtIHNxbGRmKHNxbHEpCgpoZWFkKGRmLCAzKQpgYGAKCmBgYHtyfQojIGltYWdpbmUgeW91IG5lZWQgdG8gYXNrIHVzZXIgZm9yIE9TLCAKIyBwZXJoYXBzIGJ5IGNob29zaW5nIGZyb20gYSBtZW51CnRoZU9TIDwtICJBbmRyb2lkIgoKc3FscSA8LSBwYXN0ZTAoInNlbGVjdCBnZW5kZXIsIHN1bSh0b3RhbCkgYXMgdG90YWwgCiAgICAgICAgICAgICAgICAgIGZyb20gYHR4bnNgIHdoZXJlIG9zPSciLHRoZU9TLCAiJyAKICAgICAgICAgICAgICAgICBncm91cCBieSBnZW5kZXIiKQpkZiA8LSBzcWxkZihzcWxxKQoKaGVhZChkZiwgNCkKYGBgCgojIyBKb2lucyBpbiBzcWxkZgoKKipzcWxkZioqIGlzIGFibGUgdG8gY29tYmluZSB0aGUgZGF0YSBpbiBtdWx0aXBsZSAocmVsYXRlZCkgZGF0YSBmcmFtZXMgb3IgQ1NWIGZpbGVzIHRocm91Z2ggYSBTUUwgam9pbi4gQWxsIG9mIHRoZSB1c3VhbCBqb2luIG1lY2hhbmlzbXMgb2YgU1FMIGFyZSBhdmFpbGFibGU6IGlubmVyIGpvaW4sIG91dGVyIGpvaW4sIG5hdHVyYWwgam9pbiwgYW5kIHRoZXRhIGpvaW4uCgpgYGB7ciB3YXJuaW5nPUZ9CmRmLnR4biA8LSByZWFkLmNzdigicGhhcm1hU2FsZXNUeG4uY3N2IikKZGYucmVwIDwtIHJlYWQuY3N2KCJwaGFybWFSZXBzLmNzdiIpCmBgYAoKYGBge3J9CmhlYWQoZGYudHhuLDMpCmhlYWQoZGYucmVwLDMpCmBgYAoKYGBge3Igd2FybmluZz1GfQpycyA8LSBzcWxkZigiU0VMRUNUIHIucmVwTE4gQVMgTGFzdE5hbWUsIAogICAgICAgICAgICAgICAgICAgIHByaW50ZignJCUsZCcsIHN1bSh0LmFtb3VudCkpICBBUyAnVG90YWwgU29sZCcKICAgICAgICAgICAgICAgRlJPTSBgZGYucmVwYCBBUyByIEpPSU4gYGRmLnR4bmAgQVMgdCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT04gKHIucmVwSUQgPSB0LnJlcElEKQogICAgICAgICAgICAgIEdST1VQIEJZIHQucmVwSUQiKQpgYGAKCmBgYHtyfQpoZWFkKHJzKQpgYGAKCiMjIEFwcGx5aW5nIFNRTCBEaXJlY3RseSB0byBhIENTVgoKKipzcWxkZioqIGNvbnRhaW5zIHRoZSBmdW5jdGlvbiA8Y29kZT5yZWFkLmNzdi5zcWwoKTwvY29kZT4gd2hpY2ggcmVhZHMgYSBmaWxlIGludG8gUiBhZnRlciBhcHBseWluZyBhIFNRTCBzdGF0ZW1lbnQuIE9ubHkgdGhvc2Ugcm93cyB0aGF0IG1lZXQgdGhlIFNRTCBxdWVyeSBjcml0ZXJpYSBhcmUgcHJvY2Vzc2VkIGJ5IFIuIFRoaXMgY2FuIGJlIGhlbHBmdWwgd2hlbiBwcm9jZXNzaW5nIGxhcmdlIENTViBmaWxlcy4gVGhlIGltcGxpZWQgdGFibGUgbmFtZSBpcyAqZmlsZSouCgpgYGB7ciBhcHBseVNRTDJDU1Z9CnNxbFF1ZXJ5IDwtICJTRUxFQ1QgR2VuZGVyLCBzdW0oVG90YWwpIEFTICdTdW1Ub3RhbCcgCiAgICAgICAgICAgICAgIEZST00gZmlsZSAKICAgICAgICAgICAgICBXSEVSRSBHZW5kZXIgPD4gJ05BJwogICAgICAgICAgICAgIEdST1VQIEJZIEdlbmRlciIKCnJzIDwtIHJlYWQuY3N2LnNxbCgiY3VzdG9tZXJ0eG5kYXRhLmNzdiIsIAogICAgICAgICAgICAgICAgICAgc3FsID0gc3FsUXVlcnksCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUKQoKaGVhZChycywzKQpgYGAKCk5vdGUgdGhhdCB0aGUgQ1NWIGZpbGUgaXMgcHJlc3VtZWQgdG8gaGF2ZSBhIGhlYWRlciByb3cuIFRoZSBoZWFkZXIgY29sdW1uIG5hbWVzIGFyZSB1c2VkIGFzIGNvbHVtbiBuYW1lcyBmb3IgdGhlIHZpcnR1YWwgdGFibGUuIElmIG5vIGhlYWRlciByb3cgaXMgaW4gdGhlIENTViwgdGhlbiB0aGUgcGFyYW1ldGVyICpoZWFkZXIgPSBGKiBtdXN0IGJlIHNwZWNpZmllZCBhbmQgY29sdW1ucyBuYW1lcyBhcmUgYXNzaWduZWQgYXMgKlYxKiwgKlYyKiwgYW5kIHNvIGZvcnRoLgoKVGhlIGZ1bmN0aW9uIGF0dGVtcHRzIHRvIGd1ZXNzIHRoZSBkYXRhIHR5cGVzIGZyb20gdGhlIHZhbHVlcyBidXQgdGhhdCBjYW4gYmUgY2hhbmdlZCB2aWEgdGhlICpjb2xDbGFzc2VzKiBwYXJhbWV0ZXIuIENvbHVtbiBuYW1lcyBhbmQgdHlwZXMgKHVzaW5nIFNRTCBkYXRhIHR5cGVzKSBjYW4gYmUgZG9uZSB2aWEgKmZpZWxkLnR5cGVzKi4gVGhpcyBjYW4gc2lnbmlmaWNhbnRseSBpbXByb3ZlIHJlYWQgcGVyZm9ybWFuY2UgYXMgUiBkb2VzIG5vdCBoYXZlIHRvIGRlcml2ZSBkYXRhIHR5cGVzIHZpYSBpbnNwZWN0aW9uIG9mIHZhbHVlcy4KCj4gTm90ZSB0aGF0IHF1b3RlcyBhcmUgY29uc2lkZXJlZCBwYXJ0IG9mIGEgZmllbGQuIFNvLCBpZiB0aGUgQ1NWIGZpbGUgY29udGFpbnMgIkZlbWFsZSIsIHRoZW4gdGhlIHZhbHVlIGlzIHJlYWQgaW50byBSIGFuZCB1c2VkIGJ5IFNRTCBhcyAiRmVtYWxlIiBhbmQgbm90IHNpbXBseSB0aGUgdmFsdWUgKkZlbWFsZSouCgojIyAqKnNxbGRmKiogdnMgUiBGdW5jdGlvbnMKClIgZnVuY3Rpb25zIGZvciBxdWVyeWluZyBkYXRhZnJhbWVzLCBzdWNoIGFzIDxjb2RlPndoaWNoKCk8L2NvZGU+LCBhcmUgZ2VuZXJhbGx5IHByZWZlcmFibGUgYXMgdGhleSBkbyBub3QgcmVxdWlyZSB0aGUgZGF0YWZyYW1lIHRvIGJlIGNvcGllZCB0byBhIHRlbXBvcmFyeSBkYXRhYmFzZSBpbiBvcmRlciB0byBhcHBseSBhIFNRTCBzdGF0ZW1lbnQuIFVzZSAqKnNxbGRmKiogZm9yIGdyb3VwaW5nIG9yIGFnZ3JlZ2F0aW9uIHRoYXQgYXJlIGRpZmZpY3VsdCB0byBkbyBpbiBSLiBCeSB0aGUgd2F5bSAqKnRpZHl2ZXJzZSoqIG9mZmVycyBhIHNldCBvZiBmdW5jdGlvbnMgZm9yIGdyb3VwaW5nIGFuZCBvdGhlciBzZWxlY3Rpb24gdGhhdCBhcmUgc2ltaWxhciB0byB3aGF0IGNhbiBiZSBkb25lIHdpdGggU1FMIGluICoqc3FsZGYqKi4gU28sIGFzIGlzIG9mdGVuIHRoZSBjYXNlLCB0aGVyZSdzIG1hbnkgd2F5cyB0byBkbyBhY2NvbXBsaXNoIHRoZSBzYW1lIGdvYWwgaW4gUi4KCiMjIFBvdGVudGlhbCBJc3N1ZXMKCkEgcG90ZW50aWFsIGlzc3VlIHdpdGggKipzcWxkZioqIGNhbiBvY2N1ciB3aGVuIHlvdSBjb25uZWN0IHRvIE15U1FMIG9yIGEgbm9uLVNRTGl0ZSBkYXRhYmFzZSBhcyAqKnNxbGRmKiogYXR0ZW1wdHMgdG8gdXNlIHlvdXIgZXhpc3RpbmcgZGF0YWJhc2UgY29ubmVjdGlvbiBhcyBhIGJhY2tpbmcgc3RvcmUgZm9yIGl0cyBkYXRhOyB0aGlzIHdpbGwgb2Z0ZW4gbm90IHdvcmsgZHVlIHRvIHNlY3VyaXR5IGNvbnN0cmFpbnRzLiBTbywgeW91IG5lZWQgdG8gYWRkIHRoZSBSIGNvZGUgPGNvZGU+b3B0aW9ucyhzcWxkZi5kcml2ZXIgPSAnU1FMaXRlJyk8L2NvZGU+IHdoaWNoIGZvcmNlcyAqKnNxbGRmKiogdG8gdXNlIFNRTGl0ZSBhcyBpdHMgYmFja2luZyBzdG9yZSB3aGVyZSBpdCB3aWxsIGNyZWF0ZSBhbiBpbi1tZW1vcnkgZGF0YWJhc2UuCgpJdCBpcyBub3QgdW5jb21tb24gZm9yIFIgdG8gZnJlZXplIG9yIGV2ZW4gY3Jhc2ggd2hlbiBhIFNRTCBxdWVyeSBmb3IgKipzcWxkZioqIGlzIG5vdCBwcm9wZXJseSBmb3JtYXR0ZWQuCgojIyBVcGRhdGluZyBEYXRhZnJhbWVzIHZpYSAqKnNxbGRmKioKCipEb24ndCBkbyB0aGF0Li4uKiAqKnNxbGRmKiogd29ya3Mgb24gYSB0YWJsZSBpbiB0aGUgYmFja2luZyBzdG9yZSByYXRoZXIgdGhhbiB0aGUgYWN0dWFsIGRhdGFmcmFtZSwgc28gYW55IHVwZGF0ZXMgd291bGQgaGFwcGVuIHRvIHRoYXQgdGVtcG9yYXJ5IHRhYmxlIGFuZCBub3QgdGhlIGRhdGFmcmFtZS4gU28sIG5vICpJTlNFUlQqLCAqVVBEQVRFKiwgKkRFTEVURSosIG9yICpBTFRFUiBUQUJMRSogd2l0aCAqKnNxbGRmKiouIFVzZSBzdGFuZGFyZCBSIGZ1bmN0aW9ucyBmb3IgdGhhdC4KCiMjIFdvcmtlZCBFeGFtcGxlCgpgYGB7ciBldmFsPVQsIGVjaG89VCwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CmxpYnJhcnkoc3FsZGYpCgp1cmwgPC0gImh0dHBzOi8vczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vYXJ0aWZpY2l1bS51cy9kYXRhc2V0cy9jdXN0b21lcnMuY3N2IgoKZGYgPC0gcmVhZC5jc3YodXJsLCBoZWFkZXIgPSBULCBzdHJpbmdzQXNGYWN0b3JzID0gRikKCnNxbCA8LSBwYXN0ZTAgKAogICJTRUxFQ1QgY291bnRyeSwgY291bnQoKikgYXMgYG51bWAgIiwKICAiICBGUk9NIGBkZmAgIiwKICAiIEdST1VQIEJZIGNvdW50cnkgIiwKICAiIE9SREVSIEJZIGBudW1gIERFU0MgIiwKICAiIExJTUlUIDE7IgopCgpzcWxkZihzcWwpCmBgYAoKb3IsIGFsdGVybmF0aXZlbHksCgpgYGB7ciBldmFsPVQsIGVjaG89VCwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CmxpYnJhcnkoc3FsZGYpCgp1cmwgPC0gImh0dHBzOi8vczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vYXJ0aWZpY2l1bS51cy9kYXRhc2V0cy9jdXN0b21lcnMuY3N2IgoKZGYgPC0gcmVhZC5jc3Yuc3FsKHVybCwgaGVhZGVyID0gVCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgpzcWwgPC0gcGFzdGUwICgKICAiU0VMRUNUIGNvdW50cnksIGNvdW50KCopIGFzIGBudW1gICIsCiAgIiAgRlJPTSBgZGZgICIsCiAgIiBHUk9VUCBCWSBjb3VudHJ5ICIsCiAgIiBPUkRFUiBCWSBgbnVtYCBERVNDICIsCiAgIiBMSU1JVCAxOyIKKQoKcmVhZC5jc3Yuc3FsKGZpbGUgPSB1cmwsCiAgICAgICAgICAgICBzcWwgPSBzcWwpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBGaWxlcyAmIFJlc291cmNlcwoKYGBge3IgemlwRmlsZXMsIGVjaG89RkFMU0V9CnppcE5hbWUgPSBzcHJpbnRmKCJMZXNzb25GaWxlcy0lcy0lcy56aXAiLCAKICAgICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksCiAgICAgICAgICAgICAgICAgcGFyYW1zJG51bWJlcikKCnRleHRBTGluayA9IHBhc3RlMCgiQWxsIEZpbGVzIGZvciBMZXNzb24gIiwgCiAgICAgICAgICAgICAgIHBhcmFtcyRjYXRlZ29yeSwiLiIscGFyYW1zJG51bWJlcikKCiMgZG93bmxvYWRGaWxlc0xpbmsoKSBpcyBpbmNsdWRlZCBmcm9tIF9pbnNlcnQyREIuUgprbml0cjo6cmF3X2h0bWwoZG93bmxvYWRGaWxlc0xpbmsoIi4iLCB6aXBOYW1lLCB0ZXh0QUxpbmspKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgUmVmZXJlbmNlcwoKRy4gR3JvdGhlbmRpZWNrICgyMDE3KS4gc3FsZGY6IE1hbmlwdWxhdGUgUiBEYXRhIEZyYW1lcyBVc2luZyBTUUwuIFIgcGFja2FnZSB2ZXJzaW9uIDAuNC0xMS4gPGh0dHBzOi8vQ1JBTi5SLXByb2plY3Qub3JnL3BhY2thZ2U9c3FsZGY+CgpbVGhlIHNxbGRmIFBhY2thZ2UuIFIgRG9jdW1lbnRhdGlvbi5dXSg8aHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3NxbGRmL3ZlcnNpb25zLzAuNC0xMT4pCgpbTWFrZSBSIHNwZWFrIFNRTCB3aXRoIHNxbGRmXShodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS8yMDEwLzA3L21ha2Utci1zcGVhay1zcWwtd2l0aC1zcWxkZi8pCgpbcmVhZC5jc3Yuc3FsKCkgRG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3NxbGRmL3ZlcnNpb25zLzAuNC0xMS90b3BpY3MvcmVhZC5jc3Yuc3FsKQoKIyMgRXJyYXRhCgpOb25lIGNvbGxlY3RlZCB5ZXQuIExldCB1cyBrbm93LgoKYGBgez1odG1sfQo8c2NyaXB0IHNyYz0iaHR0cHM6Ly9mb3JtLmpvdGZvcm0uY29tL3N0YXRpYy9mZWVkYmFjazIuanMiIHR5cGU9InRleHQvamF2YXNjcmlwdCI+CiAgbmV3IEpvdGZvcm1GZWVkYmFjayh7CiAgICBmb3JtSWQ6ICIyMTIxODcwNzI3ODQxNTciLAogICAgYnV0dG9uVGV4dDogIkZlZWRiYWNrIiwKICAgIGJhc2U6ICJodHRwczovL2Zvcm0uam90Zm9ybS5jb20vIiwKICAgIGJhY2tncm91bmQ6ICIjRjU5MjAyIiwKICAgIGZvbnRDb2xvcjogIiNGRkZGRkYiLAogICAgYnV0dG9uU2lkZTogImxlZnQiLAogICAgYnV0dG9uQWxpZ246ICJjZW50ZXIiLAogICAgdHlwZTogZmFsc2UsCiAgICB3aWR0aDogNzAwLAogICAgaGVpZ2h0OiA1MDAsCiAgICBpc0NhcmRGb3JtOiBmYWxzZQogIH0pOwo8L3NjcmlwdD4KYGBgCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19kZXBsb3lLbml0LlInKSksIGluY2x1ZGUgPSBGQUxTRX0KYGBgCg==