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.

library(sqldf)
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.

mean(rs$num)
## [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.

head(txns, 4)
##      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")
head(df.txn,3)
##   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
head(df.rep,3)
##   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")
head(rs)
##     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

Files & Resources

All Files for Lesson 6.330

References

G. Grothendieck (2017). sqldf: Manipulate R Data Frames Using SQL. R package version 0.4-11. https://CRAN.R-project.org/package=sqldf

[The sqldf Package. R Documentation.]](https://www.rdocumentation.org/packages/sqldf/versions/0.4-11)

Make R speak SQL with sqldf

read.csv.sql() Documentation

Errata

None collected yet. Let us know.

LS0tCnRpdGxlOiAiUXVlcnlpbmcgRGF0YSBGcmFtZXMgaW4gUiB3aXRoIDxpPnNxbGRmPC9pPiIKcGFyYW1zOgogIGNhdGVnb3J5OiA2CiAgbnVtYmVyOiAzMzAKICB0aW1lOiA0NQogIGxldmVsOiBiZWdpbm5lcgogIHRhZ3M6ICJyLFNRTCxzcWxkZixkYXRhYmFzZSxzcWxpdGUiCiAgZGVzY3JpcHRpb246ICJTaW1pbGFyIHRvIG90aGVyIGxhbmd1YWdlcywgUiBhbGxvd3MgZGF0YSBpbiBkYXRhIGZyYW1lcwogICAgICAgICAgICAgICAgKGZyb20gQ1NWLCBYTUwsIG9yIG90aGVyIHNvdXJjZXMpIHRvIGJlIHRyZWF0ZWQgYXMgaWYgdGhleQogICAgICAgICAgICAgICAgd2VyZSB0YWJsZXMgaW4gYSByZWxhdGlvbmFsIGRhdGFiYXNlIGFuZCBxdWVyeSB0aGUgZGF0YSBmcmFtZXMKICAgICAgICAgICAgICAgIHVzaW5nIFNRTC4gRGVtb25zdHJhdGVzIGhvdyB0byBmaW5kIGRhdGEgaW4gZGF0YSBmcmFtZXMsIGNvbWJpbmUgZGF0YQogICAgICAgICAgICAgICAgZnJvbSBtdWx0aXBsZSBkYXRhIGZyYW1lcywgYW5kIGFnZ3JlZ2F0ZSBkYXRhIGluIGRhdGEgZnJhbWVzCiAgICAgICAgICAgICAgICB1c2luZyBTUUwgdGhyb3VnaCB0aGUgc3FsZGYgcGFja2FnZS4iCmRhdGU6ICI8c21hbGw+YHIgU3lzLkRhdGUoKWA8L3NtYWxsPiIKYXV0aG9yOiAiPHNtYWxsPk1hcnRpbiBTY2hlZGxiYXVlcjwvc21hbGw+IgplbWFpbDogIm0uc2NoZWRsYmF1ZXJAbmV1LmVkdSIKYWZmaWxpdGF0aW9uOiAiTm9ydGhlYXN0ZXJuIFVuaXZlcnNpdHkiCm91dHB1dDogCiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgaGlnaGxpZ2h0OiB0YW5nbwotLS0KCi0tLQp0aXRsZTogIjxzbWFsbD5gciBwYXJhbXMkY2F0ZWdvcnlgLmByIHBhcmFtcyRudW1iZXJgPC9zbWFsbD48YnIvPjxzcGFuIHN0eWxlPSdjb2xvcjogIzJFNDA1MzsgZm9udC1zaXplOiAwLjllbSc+YHIgcm1hcmtkb3duOjptZXRhZGF0YSR0aXRsZWA8L3NwYW4+IgotLS0KCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19pbnNlcnQyREIuUicpKSwgaW5jbHVkZSA9IEZBTFNFfQpgYGAKCiMjIEludHJvZHVjdGlvbgoKU1FMIGlzIGEgdW5pdmVyc2FsIHF1ZXJ5IGxhbmd1YWdlLiBJdHMgdXNlIGhhcyBleHBhbmRlZCB0byBhbnkgdGFidWxhciBkYXRhIGFuZCBub3QganVzdCByZWxhdGlvbmFsIGRhdGFiYXNlcy4gKipzcWxkZioqIGlzIGEgcGFja2FnZSB0aGF0IGFsbG93cyBkYXRhIGZyYW1lcyB0byBiZSBxdWVyaWVkIHdpdGggU1FMIGFzIGlmIHRoZXkgd2VyZSB0YWJsZXMgaW4gYSBkYXRhYmFzZS4gSXQgYWxsb3dzIGEgcHJvZ3JhbW1lciBvciBkYXRhIGFuYWx5c3QgdG8gdXNlIFNRTCB0byBhY2Nlc3MsIHByb2Nlc3MsIHNlYXJjaCwgYW5kIGFnZ3JlZ2F0ZSBkYXRhIGluIGRhdGEgZnJhbWVzLgoKTWFueSBxdWVyaWVzLCB3aGlsZSBnZW5lcmFsbHkgZG9hYmxlIGluIEJhc2UgUiBvciB3aXRoICoqdGlkeXZlcnNlKiosIGFyZSBvZnRlbiBzaW1wbGVyIHdpdGggYSBTUUwgcXVlcnkgLS0gYWxiZWl0IGEgYml0IHNsb3dlciwgYnV0IHRoYXQgcmVkdWN0aW9uIGluIHBlcmZvcm1hbmNlIGlzIG9mdGVuIG5vdCBwZXJjZXB0aWJsZS4gVGhlICoqc3FsZGYqKiBwYWNrYWdlIGFjdHVhbGx5IGxvYWRzIHRoZSBkYXRhIGZyYW1lIGludG8gYW4gaW4tbWVtb3J5IFNRTGl0ZSBkYXRhYmFzZS4KCioqc3FsZGYqKiBpcyBwcmltYXJpbHkgdXNlZCB0bzoKCi0gICBzdW1tYXJpemUgb2YgZGF0YSBpbiBkYXRhIGZyYW1lcwotICAgaGFybW9uaXplIGRhdGEgYWNjZXNzIHZpYSBTUUwgZm9yIGFsbCB0YWJ1bGFyIGRhdGEKLSAgIGltcG9ydCBkYXRhIGZyb20gQ1NWIGZpbGVzCi0gICBwcm9jZXNzIHBhcnRzIG9mIGEgQ1NWCi0gICBsb2FkIGRhdGEgaW50byBkYXRhYmFzZXMKLSAgIGxlYXJuIFNRTAoKQXNpZGUgZnJvbSAqKnNxbGRmKiosIHRoZSAqKnRpZHl2ZXJzZSoqIHBhY2thZ2UgYWxzbyBjb250YWlucyBudW1lcm91cyBmdW5jdGlvbnMgZm9yIHByb2Nlc3NpbmcgZGF0YSBmcmFtZXMgaW4gYSBTUUwtbGlrZSBtYW5uZXIuCgo+ICoqc3FsZGYqKiBzaG91bGQgb25seSBiZSB1c2VkIGZvciBxdWVyaWVzIChTUUwgU0VMRUNUKSBhbmQgbm90IGZvciB1cGRhdGVzIChTUUwgSU5TRVJULCBVUERBVEUsIERFTEVURSkgYXMgdGhhdCBhY3R1YWxseSBtb2RpZmllcyBhIGNvcHkgb2YgdGhlIGRhdGEgZnJhbWUuCgojIyBTaW1wbGUgRXhhbXBsZQoKSW4gbWVtb3J5IGRhdGEgZnJhbWVzIGNhbiBiZSB0cmVhdGVkIGxpa2UgdGFibGVzIGluIGEgcmVsYXRpb25hbCBkYXRhYmFzZS4gWW91IGNhbiBzaW1wbHkgdXNlIHRoZSBuYW1lIG9mIGEgZGF0YSBmcmFtZSBpbiBhICoqc3FsZGYqKiBTUUwgcXVlcnkuIE5vdGUgdGhhdCBlYWNoICoqc3FsZGYqKiBxdWVyeSByZXR1cm5zIGEgZGF0YSBmcmFtZSAtLSBqdXN0IGxpa2UgYW55IFNRTCBxdWVyeSByZXR1cm5zIGEgdGFibGUuCgpgYGB7ciBsb2FkTGlicywgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShzcWxkZikKYGBgCgpgYGB7ciBsb2FkTGliczIsIHdhcm5pbmc9RkFMU0UsIGVjaG89VFJVRSwgZXZhbD1GQUxTRX0KbGlicmFyeShzcWxkZikKYGBgCgpgYGB7ciBkZW1vU1FMREYsIHdhcm5pbmc9Rn0KcnMgPC0gc3FsZGYoInNlbGVjdCBTcGVjaWVzLCBjb3VudCgqKSBhcyBudW0gCiAgICAgICAgICAgICAgIGZyb20gaXJpcyAKICAgICAgICAgICAgICBncm91cCBieSBTcGVjaWVzIikKaGVhZChycywgMykKYGBgCgpUaGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUgY2FuIGJlIGZ1cnRoZXIgcHJvY2Vzc2VkIGluICoqc3FsZGYqKiBvciB3aXRoIFIuCgpgYGB7cn0KbWVhbihycyRudW0pCmBgYAoKIyMgUGFyYW1ldGVycyBpbiAqKnNxbGRmKiogUXVlcmllcwoKWW91IGNhbiB1c2UgUiB2YXJpYWJsZXMgYXMgcGFyYW1ldGVycyBmb3IgKipzcWxkZioqIHF1ZXJpZXMgYnV0IHlvdSBuZWVkIHRvIGJ1aWxkIGEgc2luZ2xlIHN0cmluZyB1c2luZyA8Y29kZT5wYXN0ZTAoKTwvY29kZT4uCgpOb3RlIHRoYXQgaW4gdGhlIGV4YW1wbGUgYmVsb3csIHRoZSAqU2VwYWwuTGVuZ3RoKiBjb2x1bW4gY29udGFpbnMgYSAnZG90Jywgc28gaXQgbXVzdCBiZSBlbmNsb3NlZCBpbiBiYWNrdGlja3MuIElmIG5vdCwgdGhlbiB5b3Ugd2lsbCBnZXQgYSAiQ29sdW1uIG5vdCBmb3VuZCIgZXJyb3IuCgpgYGB7ciBzcWxkZldpdGhQYXJtc30KbiA8LSA1CnJzIDwtIHNxbGRmKHBhc3RlMCgic2VsZWN0IGNvdW50KFNwZWNpZXMpIGFzICdOdW1TcGVjaWVzJyAKICAgICAgICAgICAgICAgICAgICAgIGZyb20gaXJpcyAKICAgICAgICAgICAgICAgICAgICAgd2hlcmUgYFNlcGFsLkxlbmd0aGAgPCAiLCBuKSkKCnByaW50KHJzJE51bVNwZWNpZXMpCmBgYAoKTm90ZSB0aGF0IGNvbnN0cnVjdGluZyBxdWVyaWVzIGJ5IHBhc3RpbmcgdmFsdWVzIGludG8gYSBzaW5nbGUgc3RyaW5nIGlzIGdlbmVyYWxseSBiYWQgcHJhY3RpY2UgYW5kIGNhbiBsZWFkIHRvIFNRTCBJbmplY3Rpb24gQXR0YWNrcyBhbmQgdGhlcmVmb3JlIHNob3VsZCBiZSBhdm9pZGVkLiBIb3dldmVyLCBmb3IgZGF0YSBxdWVyeSB0YXNrcyB3aGVyZSB0aGUgcGFyYW1ldGVycyBhcmUgbm90IHVzZXIgcHJvdmlkZWQsIGl0IGlzIGFjY2VwdGFibGUgdG8gZG8uCgojIyBRdWVyeWluZyBDU1YgRGF0YQoKV2Ugd2lsbCBkZW1vbnN0cmF0ZSB0aGUgdXNlIG9mICoqc3FsZGYqKiBieSBxdWVyeWluZyB0aGUgZGF0YSBpbiBhIGxhcmdlIENTViBmaWxlLiBUaGUgZmlsZSBpcyBmaXJzdCBsb2FkZWQgaW50byBhIGRhdGEgZnJhbWUgYW5kIHRoZW4gdHJlYXRlZCBhcyBpZiBpdCB3ZXJlIGEgdGFibGUgaW4gYSBkYXRhYmFzZSBldmVuIHRob3VnaCB0aGUgZGF0YSByZXNpZGVzIGZ1bGx5IGluIG1lbW9yeS4gT2YgY291cnNlLCB0aGlzIGlzIG5vdCBzY2FsYWJsZSB0byBhcmJpdHJhcmlseSBsYXJnZSBkYXRhIGZpbGVzIGR1ZSB0byBtZW1vcnkgY29uc3RyYWludHMuCgpgYGB7ciBsb2FkQ1NWfQp0eG5zIDwtIHJlYWQuY3N2KCJjdXN0b21lcnR4bmRhdGEuY3N2IiwgCiAgICAgICAgICAgICAgIGhlYWRlciA9IEYsIAogICAgICAgICAgICAgICBjb2wubmFtZXMgPSBjKCJ2aXNpdHMiLCJudW10eG5zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAib3MiLCJnZW5kZXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0b3RhbCIpKQpgYGAKClRoZSBDU1YgZmlsZSBjb250YWlucyBkYXRhIG9uIHZpc2l0cyB0byBhbiBlLWNvbW1lcmNlIHNpdGUuCgpgYGB7ciwgZXZhbD1UfQpoZWFkKHR4bnMsIDQpCmBgYAoKKipzcWxkZioqIGV4ZWN1dGVzIGEgU1FMIHF1ZXJ5IGFnYWluc3QgYW4gaW4tbWVtb3J5IGRhdGEgZnJhbWUgYW5kIHJldHVybnMgdGhlIHF1ZXJ5IHJlc3VsdCBhcyBhIGRhdGEgZnJhbWUuIFRoZSBkYXRhIGZyYW1lIGNhbiBiZSBjYXB0dXJlZCBhbmQgdGhlbiBwcm9jZXNzZWQgZnVydGhlciB3aXRoICoqc3FsZGYqKiBvciBSLgoKYGBge3Igd2FybmluZz1GfQpsaWJyYXJ5KHNxbGRmKQoKc3FscSA8LSAic2VsZWN0IGdlbmRlciwgc3VtKHRvdGFsKSBhcyB0b3RhbCAKICAgICAgICAgICBmcm9tIGB0eG5zYCAKICAgICAgICAgIGdyb3VwIGJ5IGdlbmRlciIKCmRmIDwtIHNxbGRmKHNxbHEpCgpoZWFkKGRmLCAzKQpgYGAKCmBgYHtyfQojIGltYWdpbmUgeW91IG5lZWQgdG8gYXNrIHVzZXIgZm9yIE9TLCAKIyBwZXJoYXBzIGJ5IGNob29zaW5nIGZyb20gYSBtZW51CnRoZU9TIDwtICJBbmRyb2lkIgoKc3FscSA8LSBwYXN0ZTAoInNlbGVjdCBnZW5kZXIsIHN1bSh0b3RhbCkgYXMgdG90YWwgCiAgICAgICAgICAgICAgICAgIGZyb20gYHR4bnNgIHdoZXJlIG9zPSciLHRoZU9TLCAiJyAKICAgICAgICAgICAgICAgICBncm91cCBieSBnZW5kZXIiKQpkZiA8LSBzcWxkZihzcWxxKQoKaGVhZChkZiwgNCkKYGBgCgojIyBKb2lucyBpbiBzcWxkZgoKKipzcWxkZioqIGlzIGFibGUgdG8gY29tYmluZSB0aGUgZGF0YSBpbiBtdWx0aXBsZSAocmVsYXRlZCkgZGF0YSBmcmFtZXMgb3IgQ1NWIGZpbGVzIHRocm91Z2ggYSBTUUwgam9pbi4gQWxsIG9mIHRoZSB1c3VhbCBqb2luIG1lY2hhbmlzbXMgb2YgU1FMIGFyZSBhdmFpbGFibGU6IGlubmVyIGpvaW4sIG91dGVyIGpvaW4sIG5hdHVyYWwgam9pbiwgYW5kIHRoZXRhIGpvaW4uCgpgYGB7ciB3YXJuaW5nPUZ9CmRmLnR4biA8LSByZWFkLmNzdigicGhhcm1hU2FsZXNUeG4uY3N2IikKZGYucmVwIDwtIHJlYWQuY3N2KCJwaGFybWFSZXBzLmNzdiIpCmBgYAoKYGBge3J9CmhlYWQoZGYudHhuLDMpCmhlYWQoZGYucmVwLDMpCmBgYAoKYGBge3Igd2FybmluZz1GfQpycyA8LSBzcWxkZigiU0VMRUNUIHIucmVwTE4gQVMgTGFzdE5hbWUsIAogICAgICAgICAgICAgICAgICAgIHByaW50ZignJCUsZCcsIHN1bSh0LmFtb3VudCkpICBBUyAnVG90YWwgU29sZCcKICAgICAgICAgICAgICAgRlJPTSBgZGYucmVwYCBBUyByIEpPSU4gYGRmLnR4bmAgQVMgdCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT04gKHIucmVwSUQgPSB0LnJlcElEKQogICAgICAgICAgICAgIEdST1VQIEJZIHQucmVwSUQiKQpgYGAKCmBgYHtyfQpoZWFkKHJzKQpgYGAKCiMjIEFwcGx5aW5nIFNRTCBEaXJlY3RseSB0byBhIENTVgoKKipzcWxkZioqIGNvbnRhaW5zIHRoZSBmdW5jdGlvbiA8Y29kZT5yZWFkLmNzdi5zcWwoKTwvY29kZT4gd2hpY2ggcmVhZHMgYSBmaWxlIGludG8gUiBhZnRlciBhcHBseWluZyBhIFNRTCBzdGF0ZW1lbnQuIE9ubHkgdGhvc2Ugcm93cyB0aGF0IG1lZXQgdGhlIFNRTCBxdWVyeSBjcml0ZXJpYSBhcmUgcHJvY2Vzc2VkIGJ5IFIuIFRoaXMgY2FuIGJlIGhlbHBmdWwgd2hlbiBwcm9jZXNzaW5nIGxhcmdlIENTViBmaWxlcy4gVGhlIGltcGxpZWQgdGFibGUgbmFtZSBpcyAqZmlsZSouCgpgYGB7ciBhcHBseVNRTDJDU1Z9CnNxbFF1ZXJ5IDwtICJTRUxFQ1QgR2VuZGVyLCBzdW0oVG90YWwpIEFTICdTdW1Ub3RhbCcgCiAgICAgICAgICAgICAgIEZST00gZmlsZSAKICAgICAgICAgICAgICBXSEVSRSBHZW5kZXIgPD4gJ05BJwogICAgICAgICAgICAgIEdST1VQIEJZIEdlbmRlciIKCnJzIDwtIHJlYWQuY3N2LnNxbCgiY3VzdG9tZXJ0eG5kYXRhLmNzdiIsIAogICAgICAgICAgICAgICAgICAgc3FsID0gc3FsUXVlcnksCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUKQoKaGVhZChycywzKQpgYGAKCk5vdGUgdGhhdCB0aGUgQ1NWIGZpbGUgaXMgcHJlc3VtZWQgdG8gaGF2ZSBhIGhlYWRlciByb3cuIFRoZSBoZWFkZXIgY29sdW1uIG5hbWVzIGFyZSB1c2VkIGFzIGNvbHVtbiBuYW1lcyBmb3IgdGhlIHZpcnR1YWwgdGFibGUuIElmIG5vIGhlYWRlciByb3cgaXMgaW4gdGhlIENTViwgdGhlbiB0aGUgcGFyYW1ldGVyICpoZWFkZXIgPSBGKiBtdXN0IGJlIHNwZWNpZmllZCBhbmQgY29sdW1ucyBuYW1lcyBhcmUgYXNzaWduZWQgYXMgKlYxKiwgKlYyKiwgYW5kIHNvIGZvcnRoLgoKVGhlIGZ1bmN0aW9uIGF0dGVtcHRzIHRvIGd1ZXNzIHRoZSBkYXRhIHR5cGVzIGZyb20gdGhlIHZhbHVlcyBidXQgdGhhdCBjYW4gYmUgY2hhbmdlZCB2aWEgdGhlICpjb2xDbGFzc2VzKiBwYXJhbWV0ZXIuIENvbHVtbiBuYW1lcyBhbmQgdHlwZXMgKHVzaW5nIFNRTCBkYXRhIHR5cGVzKSBjYW4gYmUgZG9uZSB2aWEgKmZpZWxkLnR5cGVzKi4gVGhpcyBjYW4gc2lnbmlmaWNhbnRseSBpbXByb3ZlIHJlYWQgcGVyZm9ybWFuY2UgYXMgUiBkb2VzIG5vdCBoYXZlIHRvIGRlcml2ZSBkYXRhIHR5cGVzIHZpYSBpbnNwZWN0aW9uIG9mIHZhbHVlcy4KCj4gTm90ZSB0aGF0IHF1b3RlcyBhcmUgY29uc2lkZXJlZCBwYXJ0IG9mIGEgZmllbGQuIFNvLCBpZiB0aGUgQ1NWIGZpbGUgY29udGFpbnMgIkZlbWFsZSIsIHRoZW4gdGhlIHZhbHVlIGlzIHJlYWQgaW50byBSIGFuZCB1c2VkIGJ5IFNRTCBhcyAiRmVtYWxlIiBhbmQgbm90IHNpbXBseSB0aGUgdmFsdWUgKkZlbWFsZSouCgojIyAqKnNxbGRmKiogdnMgUiBGdW5jdGlvbnMKClIgZnVuY3Rpb25zIGZvciBxdWVyeWluZyBkYXRhZnJhbWVzLCBzdWNoIGFzIDxjb2RlPndoaWNoKCk8L2NvZGU+LCBhcmUgZ2VuZXJhbGx5IHByZWZlcmFibGUgYXMgdGhleSBkbyBub3QgcmVxdWlyZSB0aGUgZGF0YWZyYW1lIHRvIGJlIGNvcGllZCB0byBhIHRlbXBvcmFyeSBkYXRhYmFzZSBpbiBvcmRlciB0byBhcHBseSBhIFNRTCBzdGF0ZW1lbnQuIFVzZSAqKnNxbGRmKiogZm9yIGdyb3VwaW5nIG9yIGFnZ3JlZ2F0aW9uIHRoYXQgYXJlIGRpZmZpY3VsdCB0byBkbyBpbiBSLiBCeSB0aGUgd2F5bSAqKnRpZHl2ZXJzZSoqIG9mZmVycyBhIHNldCBvZiBmdW5jdGlvbnMgZm9yIGdyb3VwaW5nIGFuZCBvdGhlciBzZWxlY3Rpb24gdGhhdCBhcmUgc2ltaWxhciB0byB3aGF0IGNhbiBiZSBkb25lIHdpdGggU1FMIGluICoqc3FsZGYqKi4gU28sIGFzIGlzIG9mdGVuIHRoZSBjYXNlLCB0aGVyZSdzIG1hbnkgd2F5cyB0byBkbyBhY2NvbXBsaXNoIHRoZSBzYW1lIGdvYWwgaW4gUi4KCiMjIFBvdGVudGlhbCBJc3N1ZXMKCkEgcG90ZW50aWFsIGlzc3VlIHdpdGggKipzcWxkZioqIGNhbiBvY2N1ciB3aGVuIHlvdSBjb25uZWN0IHRvIE15U1FMIG9yIGEgbm9uLVNRTGl0ZSBkYXRhYmFzZSBhcyAqKnNxbGRmKiogYXR0ZW1wdHMgdG8gdXNlIHlvdXIgZXhpc3RpbmcgZGF0YWJhc2UgY29ubmVjdGlvbiBhcyBhIGJhY2tpbmcgc3RvcmUgZm9yIGl0cyBkYXRhOyB0aGlzIHdpbGwgb2Z0ZW4gbm90IHdvcmsgZHVlIHRvIHNlY3VyaXR5IGNvbnN0cmFpbnRzLiBTbywgeW91IG5lZWQgdG8gYWRkIHRoZSBSIGNvZGUgPGNvZGU+b3B0aW9ucyhzcWxkZi5kcml2ZXIgPSAnU1FMaXRlJyk8L2NvZGU+IHdoaWNoIGZvcmNlcyAqKnNxbGRmKiogdG8gdXNlIFNRTGl0ZSBhcyBpdHMgYmFja2luZyBzdG9yZSB3aGVyZSBpdCB3aWxsIGNyZWF0ZSBhbiBpbi1tZW1vcnkgZGF0YWJhc2UuCgpJdCBpcyBub3QgdW5jb21tb24gZm9yIFIgdG8gZnJlZXplIG9yIGV2ZW4gY3Jhc2ggd2hlbiBhIFNRTCBxdWVyeSBmb3IgKipzcWxkZioqIGlzIG5vdCBwcm9wZXJseSBmb3JtYXR0ZWQuCgojIyBVcGRhdGluZyBEYXRhZnJhbWVzIHZpYSAqKnNxbGRmKioKCipEb24ndCBkbyB0aGF0Li4uKiAqKnNxbGRmKiogd29ya3Mgb24gYSB0YWJsZSBpbiB0aGUgYmFja2luZyBzdG9yZSByYXRoZXIgdGhhbiB0aGUgYWN0dWFsIGRhdGFmcmFtZSwgc28gYW55IHVwZGF0ZXMgd291bGQgaGFwcGVuIHRvIHRoYXQgdGVtcG9yYXJ5IHRhYmxlIGFuZCBub3QgdGhlIGRhdGFmcmFtZS4gU28sIG5vICpJTlNFUlQqLCAqVVBEQVRFKiwgKkRFTEVURSosIG9yICpBTFRFUiBUQUJMRSogd2l0aCAqKnNxbGRmKiouIFVzZSBzdGFuZGFyZCBSIGZ1bmN0aW9ucyBmb3IgdGhhdC4KCiMjIFdvcmtlZCBFeGFtcGxlCgpgYGB7ciBldmFsPVQsIGVjaG89VCwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CmxpYnJhcnkoc3FsZGYpCgp1cmwgPC0gImh0dHBzOi8vczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vYXJ0aWZpY2l1bS51cy9kYXRhc2V0cy9jdXN0b21lcnMuY3N2IgoKZGYgPC0gcmVhZC5jc3YodXJsLCBoZWFkZXIgPSBULCBzdHJpbmdzQXNGYWN0b3JzID0gRikKCnNxbCA8LSBwYXN0ZTAgKAogICJTRUxFQ1QgY291bnRyeSwgY291bnQoKikgYXMgYG51bWAgIiwKICAiICBGUk9NIGBkZmAgIiwKICAiIEdST1VQIEJZIGNvdW50cnkgIiwKICAiIE9SREVSIEJZIGBudW1gIERFU0MgIiwKICAiIExJTUlUIDE7IgopCgpzcWxkZihzcWwpCmBgYAoKb3IsIGFsdGVybmF0aXZlbHksCgpgYGB7ciBldmFsPVQsIGVjaG89VCwgd2FybmluZz1GLCBtZXNzYWdlPUZ9CmxpYnJhcnkoc3FsZGYpCgp1cmwgPC0gImh0dHBzOi8vczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vYXJ0aWZpY2l1bS51cy9kYXRhc2V0cy9jdXN0b21lcnMuY3N2IgoKZGYgPC0gcmVhZC5jc3Yuc3FsKHVybCwgaGVhZGVyID0gVCwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgpzcWwgPC0gcGFzdGUwICgKICAiU0VMRUNUIGNvdW50cnksIGNvdW50KCopIGFzIGBudW1gICIsCiAgIiAgRlJPTSBgZGZgICIsCiAgIiBHUk9VUCBCWSBjb3VudHJ5ICIsCiAgIiBPUkRFUiBCWSBgbnVtYCBERVNDICIsCiAgIiBMSU1JVCAxOyIKKQoKcmVhZC5jc3Yuc3FsKGZpbGUgPSB1cmwsCiAgICAgICAgICAgICBzcWwgPSBzcWwpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBGaWxlcyAmIFJlc291cmNlcwoKYGBge3IgemlwRmlsZXMsIGVjaG89RkFMU0V9CnppcE5hbWUgPSBzcHJpbnRmKCJMZXNzb25GaWxlcy0lcy0lcy56aXAiLCAKICAgICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksCiAgICAgICAgICAgICAgICAgcGFyYW1zJG51bWJlcikKCnRleHRBTGluayA9IHBhc3RlMCgiQWxsIEZpbGVzIGZvciBMZXNzb24gIiwgCiAgICAgICAgICAgICAgIHBhcmFtcyRjYXRlZ29yeSwiLiIscGFyYW1zJG51bWJlcikKCiMgZG93bmxvYWRGaWxlc0xpbmsoKSBpcyBpbmNsdWRlZCBmcm9tIF9pbnNlcnQyREIuUgprbml0cjo6cmF3X2h0bWwoZG93bmxvYWRGaWxlc0xpbmsoIi4iLCB6aXBOYW1lLCB0ZXh0QUxpbmspKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgUmVmZXJlbmNlcwoKRy4gR3JvdGhlbmRpZWNrICgyMDE3KS4gc3FsZGY6IE1hbmlwdWxhdGUgUiBEYXRhIEZyYW1lcyBVc2luZyBTUUwuIFIgcGFja2FnZSB2ZXJzaW9uIDAuNC0xMS4gPGh0dHBzOi8vQ1JBTi5SLXByb2plY3Qub3JnL3BhY2thZ2U9c3FsZGY+CgpbVGhlIHNxbGRmIFBhY2thZ2UuIFIgRG9jdW1lbnRhdGlvbi5dXSg8aHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3NxbGRmL3ZlcnNpb25zLzAuNC0xMT4pCgpbTWFrZSBSIHNwZWFrIFNRTCB3aXRoIHNxbGRmXShodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS8yMDEwLzA3L21ha2Utci1zcGVhay1zcWwtd2l0aC1zcWxkZi8pCgpbcmVhZC5jc3Yuc3FsKCkgRG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3NxbGRmL3ZlcnNpb25zLzAuNC0xMS90b3BpY3MvcmVhZC5jc3Yuc3FsKQoKIyMgRXJyYXRhCgpOb25lIGNvbGxlY3RlZCB5ZXQuIExldCB1cyBrbm93LgoKYGBgez1odG1sfQo8c2NyaXB0IHNyYz0iaHR0cHM6Ly9mb3JtLmpvdGZvcm0uY29tL3N0YXRpYy9mZWVkYmFjazIuanMiIHR5cGU9InRleHQvamF2YXNjcmlwdCI+CiAgbmV3IEpvdGZvcm1GZWVkYmFjayh7CiAgICBmb3JtSWQ6ICIyMTIxODcwNzI3ODQxNTciLAogICAgYnV0dG9uVGV4dDogIkZlZWRiYWNrIiwKICAgIGJhc2U6ICJodHRwczovL2Zvcm0uam90Zm9ybS5jb20vIiwKICAgIGJhY2tncm91bmQ6ICIjRjU5MjAyIiwKICAgIGZvbnRDb2xvcjogIiNGRkZGRkYiLAogICAgYnV0dG9uU2lkZTogImxlZnQiLAogICAgYnV0dG9uQWxpZ246ICJjZW50ZXIiLAogICAgdHlwZTogZmFsc2UsCiAgICB3aWR0aDogNzAwLAogICAgaGVpZ2h0OiA1MDAsCiAgICBpc0NhcmRGb3JtOiBmYWxzZQogIH0pOwo8L3NjcmlwdD4KYGBgCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19kZXBsb3lLbml0LlInKSksIGluY2x1ZGUgPSBGQUxTRX0KYGBgCg==