PSA Training Series github.com/precision-sustainable-ag

Database Querying with R — Brian W. Davis 2020-01-07

  1. What is a DB? (video)
  2. Getting started with R (video)
  3. How to use a local DB (video)
  4. How to use the PSA DB (video)

1 Getting credentials

To follow along with this tutorial (and eventually use the database for real analyses) you need to get the login credentials. Rohit is the contact person for that, and you can see how I’ve stored them below. I have a fake file called secret-example.R that’s in this repository, so you can copy it and paste in your own username and password (without the < >).

Source file: secret-example.R

# DB credentials
pg_dbname = "<Postgres db name>"
pg_host = "<Host URL>"
pg_port = 0000
pg_user = "<your username>"
pg_password = "<your password>"

Then save it as secret.R and this code chunk will run it, storing those 5 variables in the global environment.

2 Use the credentials to connect

Now we can use them to connect to the database:

NOTE: Remember to close connections with dbDisconnect(con) at the end of your R script, but occasionally your connection will be reset without you doing it explicitly. That could be that the DB thought you were inactive, or a number of other reasons (like your WiFi is spotty). If you find that queries time out or are not responding, just reconnect using this chunk of code.

WARNING: Pay attention to the returned type of numeric values. Computers nowadays use 64-bit processors, which means they’re able to represent very large numbers in memory. However, not all programs implement them in the same way.

When we made the connection above, we specified that the Postgres data type BIGINT should be returned as regular R numeric (a.k.a. a double-precision floating point number, or 64 bits). With the scale of data we have, that won’t be a problem, as doubles can hold data up to 1.797693e+308 (which you can find with .Machine$double.xmax). If you need exact integers, you can use bigint = "integer" when you make your database connection, but that only supports values up to 2147483647 (since they’re signed 32-bit integers).

I don’t recommend using the default, which is bigint = "integer64". This is a new class of 64-bit integers which doesn’t have full support yet, so arithmetic and subsetting can have unexpected results.

2.1 Explore the DB

First thing, let’s make sure this is the right database and everything looks correct. We should see what tables are available:

 [1] "cc_families"            "decomp_biomass"        
 [3] "quadrats"               "rows"                  
 [5] "weed_research"          "yield_monitor"         
 [7] "in_field_biomass"       "site_information"      
 [9] "yield"                  "applied_chemicals"     
[11] "producer_ids"           "treatments"            
[13] "gps_locations"          "soil"                  
[15] "bulk_density"           "chemical_families"     
[17] "subsamples"             "cc_species"            
[19] "cc_mixture"             "chemical_names"        
[21] "cc_planting_methods"    "cc_termination_methods"
[23] "gps_point_types"        "cash_crops"            
[25] "codes"                  "states"                
[27] "seasons"                "textural_classes"      
[29] "depths"                 "subplots"              
[31] "texture"                "times"                 
[33] "weather"                "wsensor_allocation"    
[35] "farm_history"           "wsensor_data"          
[37] "pg_stat_statements"     "pg_buffercache"        

Let’s look at one of the tables, the list of cover crop species:

            cc_specie   cc_family
1     Annual Ryegrass     Grasses
2         Sudan grass     Grasses
3         Rape/Canola Broadleaves
4          Cereal Rye     Grasses
5          Winter Rye     Grasses
6           Triticale     Grasses
7                Oats     Grasses
8      Crimson Clover     Legumes
9          Red Clover     Legumes
10       White Clover     Legumes
11 Unknown Clover spp     Legumes
12        Hairy Vetch     Legumes
13     Woolypod Vetch     Legumes
14        Crown Vetch     Legumes
15  Unknown Vetch spp     Legumes
16      Radish/Turnip Broadleaves
17            Mustard Broadleaves
18        Winter Peas     Legumes
19            Alfalfa     Legumes
20               Kale Broadleaves
21              Wheat     Grasses
22             Barley     Grasses
23 White Cahaba Vetch     Legumes

Notice that it returned the whole table, since this was not a lazy query. For a small table like this, that’s not a problem. But some of the tables are many rows. If you try to pull them all in using dbReadTable, you’ll end up waiting a while for some tables.

# A tibble: 36 x 2
   table                        n
   <chr>                    <dbl>
 1 cc_families                  3
 2 decomp_biomass            3184
 3 quadrats                     0
 4 rows                         2
 5 weed_research                0
 6 yield_monitor                0
 7 in_field_biomass           246
 8 site_information           134
 9 yield                      651
10 applied_chemicals          105
11 producer_ids                61
12 treatments                   2
13 gps_locations                0
14 soil                       982
15 bulk_density                64
16 chemical_families            2
17 subsamples                   2
18 cc_species                  23
19 cc_mixture                 240
20 chemical_names              14
21 cc_planting_methods          4
22 cc_termination_methods       5
23 gps_point_types              0
24 cash_crops                  10
25 codes                    17576
26 states                      50
27 seasons                      2
28 textural_classes            12
29 depths                       4
30 subplots                     4
31 texture                    255
32 times                        6
33 weather                1496064
34 wsensor_allocation           0
35 farm_history               131
36 wsensor_data            783383

That’s a helpful list, but that was also a mouthful of code. Let’s break it down a bit. The following is identical code, but written with intermediate variables instead of chaining with the %>% (pipe) operator. If you’re familiar with base R instead, you could use lapply instead of purrr::map_*, but {purrr} handles a lot of details like output type for you. You don’t need to know {purrr} for the rest of this tutorial, but if you write a lot of loops, it will improve your code. The output isn’t shown below, since it’s the same as above.

3 Make some simple queries

Okay, now that we’re set up and we know some pitfalls to watch out for, let’s try a query to look at the sites enrolled. Remember, since it’s a lazy query, we don’t know how many rows there are until we collect() it.

# Source:   table<site_information> [?? x 11]
# Database: postgres [...]
     cid code  year  state county longitude latitude notes additional_cont…
   <int> <chr> <chr> <chr> <chr>      <dbl>    <dbl> <chr> <chr>           
 1  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 2  1222 TKA   2017  MD    Talbot     -76.1     39.0 <NA>  <NA>            
 3  1223 TKC   2017  MD    Talbot     -76.1     38.8 <NA>  <NA>            
 4  1224 TKD   2017  MD    Talbot     -76.0     38.7 <NA>  <NA>            
 5  1225 TCA   2017  MD    Talbot     -76.2     38.9 <NA>  <NA>            
 6  1226 TCB   2017  MD    Talbot     -76.2     38.8 <NA>  <NA>            
 7  1227 TCC   2017  MD    Talbot     -76.2     38.8 <NA>  <NA>            
 8  1228 BGR   2017  MD    Balti…     -76.6     39.6 <NA>  <NA>            
 9  1229 SRZ   2017  PA    Berks      -76.0     40.5 <NA>  <NA>            
10  1230 CLB   2017  PA    Chamb…     -77.7     39.8 <NA>  <NA>            
# … with more rows, and 2 more variables: producer_id <chr>, address <chr>

Remember that this is identical to previewing SELECT * FROM site_information in vanilla SQL. Let’s see how many sites were enrolled in each year:

# Source:   lazy query [?? x 2]
# Database: postgres [...]
  year      n
  <chr> <dbl>
1 2017     36
2 2018     43
3 2019     55
<SQL>
SELECT "year", COUNT(*) AS "n"
FROM (SELECT *
FROM "site_information"
ORDER BY "year") "dbplyr_153"
GROUP BY "year"

What about if we wanted to store a list of the site codes and what state and year they were enrolled in?

# A tibble: 134 x 5
   year  state code  latitude longitude
   <chr> <chr> <chr>    <dbl>     <dbl>
 1 2017  GA    HFS       32.6     -82.1
 2 2017  GA    IHS       33.4     -83.2
 3 2017  MD    BGR       39.6     -76.6
 4 2017  MD    CJL       39.0     -75.8
 5 2017  MD    CRD       38.9     -75.8
 6 2017  MD    CSK       38.7     -75.8
 7 2017  MD    QHS       39.2     -75.8
 8 2017  MD    SGK       38.2     -75.7
 9 2017  MD    TCA       38.9     -76.2
10 2017  MD    TCB       38.8     -76.2
# … with 124 more rows

Now you could do any local computation or manipulation you wanted. My rule of thumb is that if it’s one of the common operations (selecting columns, arrangeing to sort, filtering rows, *_joining two tables), go ahead and do those with lazy queries. If you have more complex operations you want to do, it might be better to pull the table in locally with collect(). Again, it’s a good idea to check how many rows will be returned before you do that though:

# Source:   lazy query [?? x 1]
# Database: postgres [...]
      n
  <dbl>
1   134

3.1 Mapping the sites

So let’s do some local computation on the site_list object we stored above. First we’ll make a static map using the {ggplot2} package, which is good for making high quality customizable images. Then we’ll make an interactive map with {leaflet}, a JavaScript package that embeds maps in documents like these or web applications.

We won’t fuss around too much with the options in either, but both are powerful enough to do any kind of mapping visualization you can imagine. You don’t need any of the other packages I’m using here if you aren’t making maps, but they’re handy to have if you are.

Data contains 6 rows with either missing or invalid lat/lon values and will be ignored

3.2 Reducing rows returned

With the mapping example we wanted all the sites, but some tables just have a lot of rows. You probably want to use a subset of those rows to start your model or visualize before jumping in with the whole thing.

We saw above that the largest table in the DB is the "weather" table. Let’s look at just the first few rows with a lazy query:

# Source:   table<weather> [?? x 14]
# Database: postgres [...]
      cid code  date       timez               rainfall air_temperature
    <dbl> <chr> <date>     <dttm>                 <dbl>           <dbl>
 1 2.40e6 CLA   2018-08-05 2018-08-05 17:00:00        0           29.8 
 2 2.05e6 CJL   2017-02-19 2017-02-19 06:00:00        0            9.95
 3 2.05e6 CJL   2017-02-19 2017-02-19 07:00:00        0            9.67
 4 2.05e6 CJL   2017-02-19 2017-02-19 11:00:00        0            9.49
 5 2.05e6 CJL   2017-02-19 2017-02-19 12:00:00        0            9.69
 6 2.05e6 CJL   2017-02-19 2017-02-19 15:00:00        0           15.0 
 7 2.05e6 CJL   2017-02-19 2017-02-19 16:00:00        0           16.2 
 8 2.05e6 CJL   2017-02-19 2017-02-19 17:00:00        0           17.5 
 9 2.05e6 CJL   2017-02-19 2017-02-19 21:00:00        0           19.7 
10 2.05e6 CJL   2017-02-19 2017-02-19 22:00:00        0           17.6 
# … with more rows, and 8 more variables: humidity <dbl>,
#   longwave_radiation <dbl>, shortwave_radiation <dbl>, zonal_wind <dbl>,
#   meridional_wind <dbl>, eto <dbl>, soil_temperature <dbl>,
#   soil_water_content <dbl>

We learned in the last notebook on SQLite how to filter on text and numeric columns, but there’s two new ones here, date and datetime. Really these are still both just numbers, usually counting something like the number of seconds since 1970-Jan-01, or something like that. But it’s hard to think in terms of when 18262 is, or 1577854800. You’re much more familiar with working with January 1, 2020 and 2020-01-01 12:00AM EST.

There are two main ways to work with date(time)s in R. In base R, there’s as.Date and as.POSIXct (but the documentation to use the formats is in ?strptime) and in the {tidyverse}, there’s a whole package called {lubridate}. If that seems too complicated, you’re right. Dates and times are notorious for being a bear in every programming language and on every computer platform. It’s easiest if you use an unambiguous international standard, like ISO-8601, further reading.

       dates               times                 iso
1 1984-08-23 1984-08-23 19:30:00 1984-08-23 19:30:00
2 2020-01-01 2020-01-01 00:00:00 2020-01-01 00:00:00
# A tibble: 2 x 3
  dates      times               iso                
  <date>     <dttm>              <dttm>             
1 1984-08-23 1984-08-23 19:30:00 1984-08-23 19:30:00
2 2020-01-01 2020-01-01 00:00:00 2020-01-01 00:00:00

Notice that using the standard format in your text input makes your code a lot simpler to read. Likewise, {lubridate} is pretty good at guessing, so it can be a handy shortcut as well. I’ll stick with base R for this example though, but I generally prefer the other package.

So let’s say instead of the million or so rows that "weather" has, we want just the observations from July 4, 2018. If we’re using the date column, we would need to select exact equality. But if we’re using the timez column, we want to select a range: everything greater than midnight that morning and less than 11:59:59PM that night.

# Source:   lazy query [?? x 14]
# Database: postgres [...]
      cid code  date       timez               rainfall air_temperature
    <dbl> <chr> <date>     <dttm>                 <dbl>           <dbl>
 1 2.19e6 ALR   2018-07-04 2018-07-04 00:00:00        0            30.3
 2 2.19e6 ALR   2018-07-04 2018-07-04 01:00:00        0            28.9
 3 2.19e6 ALR   2018-07-04 2018-07-04 02:00:00        0            27.4
 4 2.19e6 ALR   2018-07-04 2018-07-04 03:00:00        0            25.9
 5 2.19e6 ALR   2018-07-04 2018-07-04 04:00:00        0            25.2
 6 2.19e6 ALR   2018-07-04 2018-07-04 05:00:00        0            24.6
 7 2.19e6 ALR   2018-07-04 2018-07-04 06:00:00        0            23.9
 8 2.19e6 ALR   2018-07-04 2018-07-04 07:00:00        0            23.6
 9 2.19e6 ALR   2018-07-04 2018-07-04 08:00:00        0            23.4
10 2.19e6 ALR   2018-07-04 2018-07-04 09:00:00        0            23.1
# … with more rows, and 8 more variables: humidity <dbl>,
#   longwave_radiation <dbl>, shortwave_radiation <dbl>, zonal_wind <dbl>,
#   meridional_wind <dbl>, eto <dbl>, soil_temperature <dbl>,
#   soil_water_content <dbl>
<SQL>
SELECT *
FROM "weather"
WHERE ("date" = CAST('2018-07-04' AS DATE))
# Source:   lazy query [?? x 2]
# Database: postgres [...]
   code      n
   <chr> <dbl>
 1 ALR      24
 2 AWP      24
 3 BGS      24
 4 BRL      24
 5 BWA      24
 6 CCP      24
 7 CCS      24
 8 CDA      24
 9 CDB      24
10 CJA      24
# … with more rows

So we can see that 24 hours of observations are returned in our query for each site. Let’s try it with times instead:

# Source:   lazy query [?? x 14]
# Database: postgres [...]
      cid code  date       timez               rainfall air_temperature
    <dbl> <chr> <date>     <dttm>                 <dbl>           <dbl>
 1 2.19e6 ALR   2018-07-04 2018-07-04 04:00:00        0            25.2
 2 2.19e6 ALR   2018-07-04 2018-07-04 05:00:00        0            24.6
 3 2.19e6 ALR   2018-07-04 2018-07-04 06:00:00        0            23.9
 4 2.19e6 ALR   2018-07-04 2018-07-04 07:00:00        0            23.6
 5 2.19e6 ALR   2018-07-04 2018-07-04 08:00:00        0            23.4
 6 2.19e6 ALR   2018-07-04 2018-07-04 09:00:00        0            23.1
 7 2.19e6 ALR   2018-07-04 2018-07-04 10:00:00        0            23.9
 8 2.19e6 ALR   2018-07-04 2018-07-04 11:00:00        0            24.7
 9 2.19e6 ALR   2018-07-04 2018-07-04 12:00:00        0            25.6
10 2.19e6 ALR   2018-07-04 2018-07-04 13:00:00        0            27.0
# … with more rows, and 8 more variables: humidity <dbl>,
#   longwave_radiation <dbl>, shortwave_radiation <dbl>, zonal_wind <dbl>,
#   meridional_wind <dbl>, eto <dbl>, soil_temperature <dbl>,
#   soil_water_content <dbl>
<SQL>
SELECT *
FROM "weather"
WHERE ("timez" BETWEEN '2018-07-04T04:00:00Z' AND '2018-07-05T03:59:59Z')
# Source:   lazy query [?? x 2]
# Database: postgres [...]
   code      n
   <chr> <dbl>
 1 ALR      24
 2 AWP      24
 3 BGS      24
 4 BRL      24
 5 BWA      24
 6 CCP      24
 7 CCS      24
 8 CDA      24
 9 CDB      24
10 CJA      24
# … with more rows

We get almost the same results. Note that instead of dplyr::between, we could have said filter(timez >= t_start, timez <= t_end), and it would have been the same. Sometimes I prefer the long-winded way of writing it, if it’s clearer to read the code later.

WARNING: You have to watch your timezones carefully. You’ll notice in the translated SQL, it used '2018-07-04T04:00:00Z' as our start time. I am writing this code on a machine in the America/New_York time zone, and on July 4 2018, that was equivalent to a -4 offset (because of Daylight Saving Time, it’s -5 the rest of the year). Thus R assumed that any time I put in without an explicit timezone is my local time, while the timez column in the DB is UTC (or “Zulu”) time.

NOTE: Whether the first query (the whole of 2018-07-04 at the prime meridian) or the second query (between midnight and midnight on 2018-07-04 on the east coast) is what you want is up to you. Just be aware of what times you’re actually asking for and getting in return.

4 Joining two tables

We already explored the most important table, "site_information". It’s important, because it keys the three letter farm codes to all the other data about each farm. What about "decomp_biomass"? That’s all the data about the litterbag decomposition study, and it’s where dry weight of the cover crop biomass is stored.

# Source:   table<decomp_biomass> [?? x 14]
# Database: postgres [...]
     cid code  subplot subsample  time empty_bag_wt fresh_biomass_wt
   <int> <chr>   <int> <chr>     <int>        <dbl>            <dbl>
 1  5057 TRR         1 B             0         62.1             326.
 2  5064 TRR         1 B             2         56.6             321.
 3  5068 TRR         2 A             0         62.3             315.
 4  5075 TRR         2 B             1         53.5             306.
 5  3029 LBR         2 A             0         55.4             144.
 6  5048 RFV         2 A             3         53.5             298.
 7  4051 BWA         1 A             3         53.5              NA 
 8  4022 JYS         2 B             4         53.5             349 
 9  4016 JYS         2 A             4         56               346 
10  3974 ALR         2 B             4         52.5             135 
# … with more rows, and 7 more variables: dry_biomass_wt <dbl>,
#   crucible_wt <dbl>, tot_bwt_at_550 <dbl>, percent_c <dbl>,
#   percent_n <dbl>, recovery_date <date>, tot_bwt_at_65 <dbl>

But it only has the farm codes, so we need to join it with the site information table. What kind of join is appropriate? In the decomp data, there are multiple rows per farm code (24 if no bags got destroyed), and farm code is the only key that connects the two tables. This is called a one-to-many correspondence.

# Source:   lazy query [?? x 2]
# Database: postgres [...]
   code      n
   <chr> <dbl>
 1 ABE      20
 2 ALR      24
 3 AWP      24
 4 BBS      23
 5 BGR      24
 6 BGS      24
 7 BJD      24
 8 BNF      24
 9 BNM      20
10 BRL      24
# … with more rows
# Source:   lazy query [?? x 2]
# Database: postgres [...]
   code      n
   <chr> <dbl>
 1 AAZ       1
 2 ABE       1
 3 ALR       1
 4 AWP       1
 5 BBS       1
 6 BET       1
 7 BGR       1
 8 BGS       1
 9 BJD       1
10 BNF       1
# … with more rows

4.1 Join types

Assuming we join them with "site_information" as the “left” table and "decomp_biomass" as the “right” table:

  • A left_join() would return
    • all the columns in both tables
    • only ONE row per code
  • A right_join would return
    • all the columns in both tables
    • all the rows from the decomp table
      • including rows with no match in the site info table (NAs for lat/long etc)
  • An inner_join() would return
    • all the columns in both tables
    • MATCHING rows from both tables
      • (no NAs)
  • An outer_join() would return
    • all the columns in both tables
    • ALL rows from both tables
      • (NAs in lat/long & as NAs in dry weight)
  • A semi_join would return
    • only columns from site info
    • one row per code in the site info table
      • (the first match from decomp table, no NAs)
  • An anti_join would return
    • only columns from site info
    • ONE row per code where there’s NO MATCH in decomp data
    • (useful for finding all the mismatches/NAs)

I almost always want a full_join, so I can make sure I catch any mismatches and investigate them. A right_join is also good for “one-to-many” correspondences, as well as an inner_join. Beware of the dropped observations with inner joins; however, those rows would be “unidentifiable” because of missing data, so you’d end up dropping them in your analysis anyway. It’s just something to keep in mind.

 [1] "cid"                "code"               "year"              
 [4] "state"              "county"             "longitude"         
 [7] "latitude"           "notes"              "additional_contact"
[10] "producer_id"        "address"           
 [1] "cid"              "code"             "subplot"         
 [4] "subsample"        "time"             "empty_bag_wt"    
 [7] "fresh_biomass_wt" "dry_biomass_wt"   "crucible_wt"     
[10] "tot_bwt_at_550"   "percent_c"        "percent_n"       
[13] "recovery_date"    "tot_bwt_at_65"   

WARNING: Always look at what columns joins are matching on if you’re not specifying them manually. For example, both of these tables share the code column (which we want) as well as the cid column (which is an internal DB identifier). If we let it join on the cid column, we’re gonna have trouble, since they won’t ever match.

# Source:   lazy query [?? x 24]
# Database: postgres [...]
   cid.x code  year  state county longitude latitude notes additional_cont…
   <int> <chr> <chr> <chr> <chr>      <dbl>    <dbl> <chr> <chr>           
 1  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 2  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 3  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 4  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 5  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 6  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 7  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 8  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
 9  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
10  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
# … with more rows, and 15 more variables: producer_id <chr>, address <chr>,
#   cid.y <int>, subplot <int>, subsample <chr>, time <int>,
#   empty_bag_wt <dbl>, fresh_biomass_wt <dbl>, dry_biomass_wt <dbl>,
#   crucible_wt <dbl>, tot_bwt_at_550 <dbl>, percent_c <dbl>,
#   percent_n <dbl>, recovery_date <date>, tot_bwt_at_65 <dbl>

Notice that now there’s cid.x and cid.y, which are from the left and right tables respectively and don’t match each other. This query returns way too many columns, so let’s simplify the data so it’s easier to look at:

<SQL>
SELECT "code", "year", "state", "subplot", "subsample", "time", "dry_biomass_wt", "percent_n"
FROM (SELECT "LHS"."cid" AS "cid.x", "LHS"."code" AS "code", "LHS"."year" AS "year", "LHS"."state" AS "state", "LHS"."county" AS "county", "LHS"."longitude" AS "longitude", "LHS"."latitude" AS "latitude", "LHS"."notes" AS "notes", "LHS"."additional_contact" AS "additional_contact", "LHS"."producer_id" AS "producer_id", "LHS"."address" AS "address", "RHS"."cid" AS "cid.y", "RHS"."subplot" AS "subplot", "RHS"."subsample" AS "subsample", "RHS"."time" AS "time", "RHS"."empty_bag_wt" AS "empty_bag_wt", "RHS"."fresh_biomass_wt" AS "fresh_biomass_wt", "RHS"."dry_biomass_wt" AS "dry_biomass_wt", "RHS"."crucible_wt" AS "crucible_wt", "RHS"."tot_bwt_at_550" AS "tot_bwt_at_550", "RHS"."percent_c" AS "percent_c", "RHS"."percent_n" AS "percent_n", "RHS"."recovery_date" AS "recovery_date", "RHS"."tot_bwt_at_65" AS "tot_bwt_at_65"
FROM "site_information" AS "LHS"
INNER JOIN "decomp_biomass" AS "RHS"
ON ("LHS"."code" = "RHS"."code")
) "dbplyr_157"
# Source:   lazy query [?? x 8]
# Database: postgres [...]
   code  year  state subplot subsample  time dry_biomass_wt percent_n
   <chr> <chr> <chr>   <int> <chr>     <int>          <dbl>     <dbl>
 1 CSK   2017  MD          1 A             0           92.2      2.71
 2 CSK   2017  MD          1 A             1           91        2.75
 3 CSK   2017  MD          1 A             2           84        3   
 4 CSK   2017  MD          1 A             3           81.2      2.67
 5 CSK   2017  MD          1 A             4           68.4      2.57
 6 CSK   2017  MD          1 A             5           58.4      2.58
 7 CSK   2017  MD          1 B             0           98.6      2.42
 8 CSK   2017  MD          1 B             1           83.9      3.12
 9 CSK   2017  MD          1 B             2           83.6      2.92
10 CSK   2017  MD          1 B             3           76.3      2.66
# … with more rows

That nasty SQL statement could be simplified to:

But again, that would return the whole query at once, instead of lazily, so if you want to iterate over different versions of your query for testing, it can be slow and eat up memory. All the explicit naming and quoting that {dbplyr} is doing when it translates to SQL prevents a lot of errors, but if your hand-written SQL is better than mine, you can use it straight instead.

Let’s look at rows that have already had the bags run on the C:N analyzer to get %N content:

# A tibble: 413 x 8
   code  year  state subplot subsample  time dry_biomass_wt percent_n
   <chr> <chr> <chr>   <int> <chr>     <int>          <dbl>     <dbl>
 1 CSK   2017  MD          2 B             4           74.7      2.76
 2 SGK   2017  MD          1 B             3           67.6      2.96
 3 TKA   2017  MD          1 A             0           61.9      3.21
 4 KTC   2018  MD          2 A             0           62        4.64
 5 KTE   2018  MD          1 A             0           64.7      2.85
 6 CJL   2017  MD          2 B             2           74.1      1.61
 7 QAS   2018  MD          1 A             0           59.7      2.92
 8 CSA   2018  MD          1 A             0           91.8      1.84
 9 QAS   2018  MD          2 A             0           59.9      3.00
10 TKA   2017  MD          1 A             1           59.6      3.69
# … with 403 more rows

5 More complex queries

Let’s try an example with three tables. I’ll split it up into several separate queries to make it a little more clear what each step is doing, but they could all be chained together into a single statement.

  1. We’ll use the decomp_biomass table to estimate moisture content for bags at time == 0 (first collection).
  2. Then we’ll join it to the in_field_biomass table and calculate dry equivalents of the fresh biomass there.
  3. Then we’ll join it back to the site_information table to get state and year information.
  4. Then we’ll convert the percent-quality columns into total \(grams/meter^{2}\) units.
  5. Finally we’ll pull the query in locally and do some visualization and analysis.

5.1 Moisture content estimates

# Source:   lazy query [?? x 3]
# Database: postgres [...]
# Groups:   code
   code  subplot m_ratio
   <chr>   <int>   <dbl>
 1 ALR         1   0.646
 2 ALR         2   0.661
 3 AWP         1   0.416
 4 AWP         2   0.426
 5 BBS         1   0.334
 6 BBS         2   0.297
 7 BGR         1   0.276
 8 BGR         2   0.268
 9 BGS         1   0.278
10 BGS         2   0.344
# … with more rows

Here we looked up the decomp table, filtered to only the time-zero bags, and calculated the ratio of dry:fresh biomass weights. Then we separate it into groups for each farm code and subplot (should be two rows per group, subsamples A and B). For each group, we then calculate the mean of that moisture ratio.

5.2 Get dry matter equivalents for bulk cover crop material

Joining, by = c("code", "subplot")
# Source:   lazy query [?? x 25]
# Database: postgres [...]
# Groups:   code
   code  subplot m_ratio   cid percent_fat percent_cp percent_ash percent_adf
   <chr>   <int>   <dbl> <int>       <dbl>      <dbl>       <dbl>       <dbl>
 1 SRY         1   0.374  1201       NA         NA          NA           NA  
 2 FSB         2   0.491  1198       NA         NA          NA           NA  
 3 SRY         2   0.425  1202       NA         NA          NA           NA  
 4 ULB         1   0.282   908        2.32      13.2         7.44        32.6
 5 ULB         2   0.358   909        1.95      11.6         6.6         34.7
 6 MKD         2   0.298   995        3.2       13.7         5.4         22.8
 7 BRL         1   0.276   966        2.84      18.0         8.91        25.2
 8 STA         1   0.331  1223        2.16       9.63        5.09        31.1
 9 DAE         1   0.438  1219        2.3        7.48        2.62        25.8
10 DAE         2   0.381  1220        2.4        6.99        3.67        26.9
# … with more rows, and 17 more variables: percent_ndf <dbl>,
#   percent_lignin <dbl>, percent_nfc <dbl>, percent_cellulose_calc <dbl>,
#   percent_hemicell_calc <dbl>, percent_carb_nnorm <dbl>,
#   percent_cellulose_nnorm <dbl>, percent_lignin_nnorm <dbl>,
#   percent_carb_norm <dbl>, percent_cellulose_norm <dbl>,
#   percent_lignin_norm <dbl>, fresh_wt_a <dbl>, fresh_wt_b <dbl>,
#   bag_wt <dbl>, legumes_40 <lgl>, percent_n_nir <dbl>,
#   dry_wt_est_g_m2 <dbl>

The first query (bag_moisture_query) constructed a temporary table inside the database. Now we’re telling the DB we want to do a full (or outer) join on that temporary table and the in_field_biomass table (containing all matches between rows, and every column in both tables). Since we constructed the temporary table to only have three columns (code, subplot, and m_ratio), we don’t have to worry about specifying which columns to join on: we should get perfect 1:1 matching on code and subplot. A helpful message is printed letting us know those are the common columns.

After the join, we then make a new column in this new temporary table. The new column is the average of the two subsamples of fresh weights, times the moisture ratio we calculated before. Again, simple arithmetic operations like this are easy for the database to do internally. Now we have a temporary table with all the biomass properties, including an estimate of dry matter content in \(grams/meter^{2}\).

5.3 Combine with site info

# Source:     lazy query [?? x 35]
# Database:   postgres [...]
# Ordered by: year, state
   cid.x code  year  state county longitude latitude notes additional_cont…
   <int> <chr> <chr> <chr> <chr>      <dbl>    <dbl> <chr> <chr>           
 1  1085 HFS   2017  GA    Burke      -82.1     32.6 <NA>  <NA>            
 2  1086 IHS   2017  GA    Oconee     -83.2     33.4 <NA>  <NA>            
 3  1085 HFS   2017  GA    Burke      -82.1     32.6 <NA>  <NA>            
 4  1086 IHS   2017  GA    Oconee     -83.2     33.4 <NA>  <NA>            
 5  1228 BGR   2017  MD    Balti…     -76.6     39.6 <NA>  <NA>            
 6  1231 CRD   2017  MD    Carol…     -75.8     38.9 <NA>  <NA>            
 7  1231 CRD   2017  MD    Carol…     -75.8     38.9 <NA>  <NA>            
 8  1234 CJL   2017  MD    Carol…     -75.8     39.0 <NA>  <NA>            
 9  1234 CJL   2017  MD    Carol…     -75.8     39.0 <NA>  <NA>            
10  1221 CSK   2017  MD    Carol…     -75.8     38.7 <NA>  <NA>            
# … with more rows, and 26 more variables: producer_id <chr>, address <chr>,
#   subplot <int>, m_ratio <dbl>, cid.y <int>, percent_fat <dbl>,
#   percent_cp <dbl>, percent_ash <dbl>, percent_adf <dbl>,
#   percent_ndf <dbl>, percent_lignin <dbl>, percent_nfc <dbl>,
#   percent_cellulose_calc <dbl>, percent_hemicell_calc <dbl>,
#   percent_carb_nnorm <dbl>, percent_cellulose_nnorm <dbl>,
#   percent_lignin_nnorm <dbl>, percent_carb_norm <dbl>,
#   percent_cellulose_norm <dbl>, percent_lignin_norm <dbl>,
#   fresh_wt_a <dbl>, fresh_wt_b <dbl>, bag_wt <dbl>, legumes_40 <lgl>,
#   percent_n_nir <dbl>, dry_wt_est_g_m2 <dbl>

Now we take the temporary table and do another full/outer join, this time with the site info table. Since we didn’t strip out any columns in the last query, it’s a good idea to specify by = "code" to make sure it ignores any other common columns (in particular the internal row identifier, cid). Now we have a temporary table with all the biomass properties, as well as information about which state and year they were recorded in.

5.4 Convert units

<SQL>
SELECT "cid.x", "code", "year", "state", "county", "longitude", "latitude", "notes", "additional_contact", "producer_id", "address", "subplot", "m_ratio", "cid.y", "percent_fat", "percent_cp", "percent_ash", "percent_adf", "percent_ndf", "percent_lignin", "percent_nfc", "percent_cellulose_calc", "percent_hemicell_calc", "percent_carb_nnorm", "percent_cellulose_nnorm", "percent_lignin_nnorm", "percent_carb_norm", "percent_cellulose_norm", "percent_lignin_norm", "fresh_wt_a", "fresh_wt_b", "bag_wt", "legumes_40", "percent_n_nir", "dry_wt_est_g_m2", "percent_fat" * "dry_wt_est_g_m2" / 100.0 AS "percent_fat_g_m2", "percent_cp" * "dry_wt_est_g_m2" / 100.0 AS "percent_cp_g_m2", "percent_ash" * "dry_wt_est_g_m2" / 100.0 AS "percent_ash_g_m2", "percent_adf" * "dry_wt_est_g_m2" / 100.0 AS "percent_adf_g_m2", "percent_ndf" * "dry_wt_est_g_m2" / 100.0 AS "percent_ndf_g_m2", "percent_lignin" * "dry_wt_est_g_m2" / 100.0 AS "percent_lignin_g_m2", "percent_nfc" * "dry_wt_est_g_m2" / 100.0 AS "percent_nfc_g_m2", "percent_cellulose_calc" * "dry_wt_est_g_m2" / 100.0 AS "percent_cellulose_calc_g_m2", "percent_hemicell_calc" * "dry_wt_est_g_m2" / 100.0 AS "percent_hemicell_calc_g_m2", "percent_carb_nnorm" * "dry_wt_est_g_m2" / 100.0 AS "percent_carb_nnorm_g_m2", "percent_cellulose_nnorm" * "dry_wt_est_g_m2" / 100.0 AS "percent_cellulose_nnorm_g_m2", "percent_lignin_nnorm" * "dry_wt_est_g_m2" / 100.0 AS "percent_lignin_nnorm_g_m2", "percent_carb_norm" * "dry_wt_est_g_m2" / 100.0 AS "percent_carb_norm_g_m2", "percent_cellulose_norm" * "dry_wt_est_g_m2" / 100.0 AS "percent_cellulose_norm_g_m2", "percent_lignin_norm" * "dry_wt_est_g_m2" / 100.0 AS "percent_lignin_norm_g_m2", "percent_n_nir" * "dry_wt_est_g_m2" / 100.0 AS "percent_n_nir_g_m2"
FROM (SELECT *
FROM (SELECT "LHS"."cid" AS "cid.x", COALESCE("LHS"."code", "RHS"."code") AS "code", "LHS"."year" AS "year", "LHS"."state" AS "state", "LHS"."county" AS "county", "LHS"."longitude" AS "longitude", "LHS"."latitude" AS "latitude", "LHS"."notes" AS "notes", "LHS"."additional_contact" AS "additional_contact", "LHS"."producer_id" AS "producer_id", "LHS"."address" AS "address", "RHS"."subplot" AS "subplot", "RHS"."m_ratio" AS "m_ratio", "RHS"."cid" AS "cid.y", "RHS"."percent_fat" AS "percent_fat", "RHS"."percent_cp" AS "percent_cp", "RHS"."percent_ash" AS "percent_ash", "RHS"."percent_adf" AS "percent_adf", "RHS"."percent_ndf" AS "percent_ndf", "RHS"."percent_lignin" AS "percent_lignin", "RHS"."percent_nfc" AS "percent_nfc", "RHS"."percent_cellulose_calc" AS "percent_cellulose_calc", "RHS"."percent_hemicell_calc" AS "percent_hemicell_calc", "RHS"."percent_carb_nnorm" AS "percent_carb_nnorm", "RHS"."percent_cellulose_nnorm" AS "percent_cellulose_nnorm", "RHS"."percent_lignin_nnorm" AS "percent_lignin_nnorm", "RHS"."percent_carb_norm" AS "percent_carb_norm", "RHS"."percent_cellulose_norm" AS "percent_cellulose_norm", "RHS"."percent_lignin_norm" AS "percent_lignin_norm", "RHS"."fresh_wt_a" AS "fresh_wt_a", "RHS"."fresh_wt_b" AS "fresh_wt_b", "RHS"."bag_wt" AS "bag_wt", "RHS"."legumes_40" AS "legumes_40", "RHS"."percent_n_nir" AS "percent_n_nir", "RHS"."dry_wt_est_g_m2" AS "dry_wt_est_g_m2"
FROM "site_information" AS "LHS"
FULL JOIN (SELECT "code", "subplot", "m_ratio", "cid", "percent_fat", "percent_cp", "percent_ash", "percent_adf", "percent_ndf", "percent_lignin", "percent_nfc", "percent_cellulose_calc", "percent_hemicell_calc", "percent_carb_nnorm", "percent_cellulose_nnorm", "percent_lignin_nnorm", "percent_carb_norm", "percent_cellulose_norm", "percent_lignin_norm", "fresh_wt_a", "fresh_wt_b", "bag_wt", "legumes_40", "percent_n_nir", "m_ratio" * ("fresh_wt_a" + "fresh_wt_b") / 2.0 AS "dry_wt_est_g_m2"
FROM (SELECT COALESCE("LHS"."code", "RHS"."code") AS "code", COALESCE("LHS"."subplot", "RHS"."subplot") AS "subplot", "LHS"."m_ratio" AS "m_ratio", "RHS"."cid" AS "cid", "RHS"."percent_fat" AS "percent_fat", "RHS"."percent_cp" AS "percent_cp", "RHS"."percent_ash" AS "percent_ash", "RHS"."percent_adf" AS "percent_adf", "RHS"."percent_ndf" AS "percent_ndf", "RHS"."percent_lignin" AS "percent_lignin", "RHS"."percent_nfc" AS "percent_nfc", "RHS"."percent_cellulose_calc" AS "percent_cellulose_calc", "RHS"."percent_hemicell_calc" AS "percent_hemicell_calc", "RHS"."percent_carb_nnorm" AS "percent_carb_nnorm", "RHS"."percent_cellulose_nnorm" AS "percent_cellulose_nnorm", "RHS"."percent_lignin_nnorm" AS "percent_lignin_nnorm", "RHS"."percent_carb_norm" AS "percent_carb_norm", "RHS"."percent_cellulose_norm" AS "percent_cellulose_norm", "RHS"."percent_lignin_norm" AS "percent_lignin_norm", "RHS"."fresh_wt_a" AS "fresh_wt_a", "RHS"."fresh_wt_b" AS "fresh_wt_b", "RHS"."bag_wt" AS "bag_wt", "RHS"."legumes_40" AS "legumes_40", "RHS"."percent_n_nir" AS "percent_n_nir"
FROM (SELECT "code", "subplot", AVG("m_ratio") AS "m_ratio"
FROM (SELECT "cid", "code", "subplot", "subsample", "time", "empty_bag_wt", "fresh_biomass_wt", "dry_biomass_wt", "crucible_wt", "tot_bwt_at_550", "percent_c", "percent_n", "recovery_date", "tot_bwt_at_65", "dry_biomass_wt" / "fresh_biomass_wt" AS "m_ratio"
FROM (SELECT *
FROM "decomp_biomass"
WHERE ("time" = 0.0)) "dbplyr_170") "dbplyr_171"
GROUP BY "code", "subplot") "LHS"
FULL JOIN "in_field_biomass" AS "RHS"
ON ("LHS"."code" = "RHS"."code" AND "LHS"."subplot" = "RHS"."subplot")
) "dbplyr_172") "RHS"
ON ("LHS"."code" = "RHS"."code")
) "dbplyr_173"
ORDER BY "year", "state") "dbplyr_174"

The last step is to create new columns using an “anonymous function”: ~.*dry_wt_est_g_m2/100. This is the same thing as a function that looks like multiplier <- function(pct) { pct * dry_wt_est_g_m2 / 100}. We then use that function on all the variables/columns (vars()) that match the text "percent_". For every such column, it will now make a new column with the name percent_foo_g_m2, which will be the total amount of “foo” in \(grams/meter^{2}\).

You can also see I printed out the generated SQL query, since this is still all happening inside the database. You could have pulled the query in locally at a number of points during this chain, and the {dplyr} syntax would have been identical (the calls to filter, mutate, summarize, group_by, full_join); just the calls to tbl(con, "table_name") would have been your local variables instead.

Again, this is overly-explicit generated SQL, which could probably be simplified if you wrote it by hand. However, the {dbplyr}-translated version checks for a lot of possible errors and tries to optimize the number of operations for speed and memory-efficiency.

5.5 Complete the query

# A tibble: 259 x 51
   cid.x code  year  state county longitude latitude notes additional_cont…
   <int> <chr> <chr> <chr> <chr>      <dbl>    <dbl> <chr> <chr>           
 1  1086 IHS   2017  GA    Oconee     -83.2     33.4 <NA>  <NA>            
 2  1086 IHS   2017  GA    Oconee     -83.2     33.4 <NA>  <NA>            
 3  1085 HFS   2017  GA    Burke      -82.1     32.6 <NA>  <NA>            
 4  1085 HFS   2017  GA    Burke      -82.1     32.6 <NA>  <NA>            
 5  1223 TKC   2017  MD    Talbot     -76.1     38.8 <NA>  <NA>            
 6  1226 TCB   2017  MD    Talbot     -76.2     38.8 <NA>  <NA>            
 7  1233 QHS   2017  MD    Queen…     -75.8     39.2 <NA>  <NA>            
 8  1225 TCA   2017  MD    Talbot     -76.2     38.9 <NA>  <NA>            
 9  1231 CRD   2017  MD    Carol…     -75.8     38.9 <NA>  <NA>            
10  1226 TCB   2017  MD    Talbot     -76.2     38.8 <NA>  <NA>            
# … with 249 more rows, and 42 more variables: producer_id <chr>,
#   address <chr>, subplot <int>, m_ratio <dbl>, cid.y <int>,
#   percent_fat <dbl>, percent_cp <dbl>, percent_ash <dbl>,
#   percent_adf <dbl>, percent_ndf <dbl>, percent_lignin <dbl>,
#   percent_nfc <dbl>, percent_cellulose_calc <dbl>,
#   percent_hemicell_calc <dbl>, percent_carb_nnorm <dbl>,
#   percent_cellulose_nnorm <dbl>, percent_lignin_nnorm <dbl>,
#   percent_carb_norm <dbl>, percent_cellulose_norm <dbl>,
#   percent_lignin_norm <dbl>, fresh_wt_a <dbl>, fresh_wt_b <dbl>,
#   bag_wt <dbl>, legumes_40 <lgl>, percent_n_nir <dbl>,
#   dry_wt_est_g_m2 <dbl>, percent_fat_g_m2 <dbl>, percent_cp_g_m2 <dbl>,
#   percent_ash_g_m2 <dbl>, percent_adf_g_m2 <dbl>, percent_ndf_g_m2 <dbl>,
#   percent_lignin_g_m2 <dbl>, percent_nfc_g_m2 <dbl>,
#   percent_cellulose_calc_g_m2 <dbl>, percent_hemicell_calc_g_m2 <dbl>,
#   percent_carb_nnorm_g_m2 <dbl>, percent_cellulose_nnorm_g_m2 <dbl>,
#   percent_lignin_nnorm_g_m2 <dbl>, percent_carb_norm_g_m2 <dbl>,
#   percent_cellulose_norm_g_m2 <dbl>, percent_lignin_norm_g_m2 <dbl>,
#   percent_n_nir_g_m2 <dbl>

Now all we had left was to pull that long 3-table query into your local R environment and do some visualization and analysis.

6 And don’t forget to clean up after

LS0tCnRpdGxlOiAiMDA0IC0gSG93IHRvIHVzZSB0aGUgUFNBIERCIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogcmVhZGFibGUKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgY29kZV9mb2xkaW5nOiBub25lCiAgICBpbmNsdWRlczoKICAgICAgYmVmb3JlX2JvZHk6IGFzc2V0cy9oZWFkZXIuaHRtbAogICAgICBhZnRlcl9ib2R5OiBhc3NldHMvZm9vdGVyLmh0bWwKLS0tCgojIEdldHRpbmcgY3JlZGVudGlhbHMKClRvIGZvbGxvdyBhbG9uZyB3aXRoIHRoaXMgdHV0b3JpYWwgKGFuZCBldmVudHVhbGx5IHVzZSB0aGUgZGF0YWJhc2UgZm9yIHJlYWwgYW5hbHlzZXMpIHlvdSBuZWVkIHRvIGdldCB0aGUgbG9naW4gY3JlZGVudGlhbHMuIFJvaGl0IGlzIHRoZSBjb250YWN0IHBlcnNvbiBmb3IgdGhhdCwgYW5kIHlvdSBjYW4gc2VlIGhvdyBJJ3ZlIHN0b3JlZCB0aGVtIGJlbG93LiBJIGhhdmUgYSBmYWtlIGZpbGUgY2FsbGVkIGBzZWNyZXQtZXhhbXBsZS5SYCB0aGF0J3MgaW4gdGhpcyByZXBvc2l0b3J5LCBzbyB5b3UgY2FuIGNvcHkgaXQgYW5kIHBhc3RlIGluIHlvdXIgb3duIHVzZXJuYW1lIGFuZCBwYXNzd29yZCAod2l0aG91dCB0aGUgKipgPCA+YCoqKS4KClNvdXJjZSBmaWxlOiBbYHNlY3JldC1leGFtcGxlLlJgXShzZWNyZXQtZXhhbXBsZS5SKQoKYGBge3IgZWNobz1GQUxTRX0KY2F0KHJlYWRMaW5lcygic2VjcmV0LWV4YW1wbGUuUiIpLCBzZXAgPSAiXG4iKQpgYGAKClRoZW4gc2F2ZSBpdCBhcyBgc2VjcmV0LlJgIGFuZCB0aGlzIGNvZGUgY2h1bmsgd2lsbCBydW4gaXQsIHN0b3JpbmcgdGhvc2UgNSB2YXJpYWJsZXMgaW4gdGhlIGdsb2JhbCBlbnZpcm9ubWVudC4KCmBgYHtyfQpzb3VyY2UoInNlY3JldC5SIikKYGBgCgojIFVzZSB0aGUgY3JlZGVudGlhbHMgdG8gY29ubmVjdAoKTm93IHdlIGNhbiB1c2UgdGhlbSB0byBjb25uZWN0IHRvIHRoZSBkYXRhYmFzZToKCmBgYHtyIGVjaG8gPSBGQUxTRX0KIyBDbGVhbiB1cCBhbiBvbGQgY29ubmVjdGlvbiBiZWZvcmUgYWRkaW5nIGEgbmV3IG9uZQoKaWYgKGV4aXN0cygiY29uIikgJiYgCiAgICBjbGFzcyhjb24pID09ICJQcUNvbm5lY3Rpb24iICYmIAogICAgREJJOjpkYklzVmFsaWQoY29uKSAmJgogICAgREJJOjpkYkdldEluZm8oY29uKSRkYm5hbWUgPT0gImNyb3duZGIiKSB7CiAgREJJOjpkYkRpc2Nvbm5lY3QoY29uKQogIGludmlzaWJsZSgpCn0KYGBgCgpgYGB7cn0KbGlicmFyeShkcGx5ciwgd2Fybi5jb25mbGljdHMgPSBGQUxTRSkKbGlicmFyeShkYnBseXIsIHdhcm4uY29uZmxpY3RzID0gRkFMU0UpCmxpYnJhcnkoUlBvc3RncmVzKQoKY29uIDwtIGRiQ29ubmVjdCgKICBQb3N0Z3JlcygpLAogIGRibmFtZSA9IHBnX2RibmFtZSwKICBob3N0ID0gcGdfaG9zdCwKICBwb3J0ID0gcGdfcG9ydCwKICB1c2VyID0gcGdfdXNlciwKICBwYXNzd29yZCA9IHBnX3Bhc3N3b3JkLAogIHNzbG1vZGUgPSAicmVxdWlyZSIsCiAgYmlnaW50ID0gIm51bWVyaWMiCikKYGBgCgo+ICoqTk9URToqKiBSZW1lbWJlciB0byBjbG9zZSBjb25uZWN0aW9ucyB3aXRoICoqYGRiRGlzY29ubmVjdChjb24pYCoqIGF0IHRoZSBlbmQgb2YgeW91ciBSIHNjcmlwdCwgYnV0IG9jY2FzaW9uYWxseSB5b3VyIGNvbm5lY3Rpb24gd2lsbCBiZSByZXNldCB3aXRob3V0IHlvdSBkb2luZyBpdCBleHBsaWNpdGx5LiBUaGF0IGNvdWxkIGJlIHRoYXQgdGhlIERCIHRob3VnaHQgeW91IHdlcmUgaW5hY3RpdmUsIG9yIGEgbnVtYmVyIG9mIG90aGVyIHJlYXNvbnMgKGxpa2UgeW91ciBXaUZpIGlzIHNwb3R0eSkuIElmIHlvdSBmaW5kIHRoYXQgcXVlcmllcyB0aW1lIG91dCBvciBhcmUgbm90IHJlc3BvbmRpbmcsIGp1c3QgcmVjb25uZWN0IHVzaW5nIHRoaXMgY2h1bmsgb2YgY29kZS4KCj4gKipXQVJOSU5HOioqIFBheSBhdHRlbnRpb24gdG8gdGhlIHJldHVybmVkIHR5cGUgb2YgbnVtZXJpYyB2YWx1ZXMuIENvbXB1dGVycyBub3dhZGF5cyB1c2UgNjQtYml0IHByb2Nlc3NvcnMsIHdoaWNoIG1lYW5zIHRoZXkncmUgYWJsZSB0byByZXByZXNlbnQgdmVyeSBsYXJnZSBudW1iZXJzIGluIG1lbW9yeS4gSG93ZXZlciwgbm90IGFsbCBwcm9ncmFtcyBpbXBsZW1lbnQgdGhlbSBpbiB0aGUgc2FtZSB3YXkuIAo+Cj4gV2hlbiB3ZSBtYWRlIHRoZSBjb25uZWN0aW9uIGFib3ZlLCB3ZSBzcGVjaWZpZWQgdGhhdCB0aGUgUG9zdGdyZXMgZGF0YSB0eXBlICoqYEJJR0lOVGAqKiBzaG91bGQgYmUgcmV0dXJuZWQgYXMgcmVndWxhciBSICoqYG51bWVyaWNgKiogKGEuay5hLiBhICoqYGRvdWJsZWAqKi1wcmVjaXNpb24gZmxvYXRpbmcgcG9pbnQgbnVtYmVyLCBvciA2NCBiaXRzKS4gV2l0aCB0aGUgc2NhbGUgb2YgZGF0YSB3ZSBoYXZlLCB0aGF0IHdvbid0IGJlIGEgcHJvYmxlbSwgYXMgKipgZG91YmxlYCoqcyBjYW4gaG9sZCBkYXRhIHVwIHRvIGAxLjc5NzY5M2UrMzA4YCAod2hpY2ggeW91IGNhbiBmaW5kIHdpdGggYC5NYWNoaW5lJGRvdWJsZS54bWF4YCkuIElmIHlvdSBuZWVkIGV4YWN0IGludGVnZXJzLCB5b3UgY2FuIHVzZSAqKmBiaWdpbnQgPSAiaW50ZWdlciJgKiogd2hlbiB5b3UgbWFrZSB5b3VyIGRhdGFiYXNlIGNvbm5lY3Rpb24sIGJ1dCB0aGF0IG9ubHkgc3VwcG9ydHMgdmFsdWVzIHVwIHRvIGAyMTQ3NDgzNjQ3YCAoc2luY2UgdGhleSdyZSBzaWduZWQgMzItYml0IGludGVnZXJzKS4gCj4KPiBJIGRvbid0IHJlY29tbWVuZCB1c2luZyB0aGUgZGVmYXVsdCwgd2hpY2ggaXMgYGJpZ2ludCA9ICJpbnRlZ2VyNjQiYC4gVGhpcyBpcyBhIG5ldyBjbGFzcyBvZiA2NC1iaXQgaW50ZWdlcnMgd2hpY2ggZG9lc24ndCBoYXZlIGZ1bGwgc3VwcG9ydCB5ZXQsIHNvIGFyaXRobWV0aWMgYW5kIHN1YnNldHRpbmcgY2FuIGhhdmUgdW5leHBlY3RlZCByZXN1bHRzLgoKIyMgRXhwbG9yZSB0aGUgREIKCkZpcnN0IHRoaW5nLCBsZXQncyBtYWtlIHN1cmUgdGhpcyBpcyB0aGUgcmlnaHQgZGF0YWJhc2UgYW5kIGV2ZXJ5dGhpbmcgbG9va3MgY29ycmVjdC4gV2Ugc2hvdWxkIHNlZSB3aGF0IHRhYmxlcyBhcmUgYXZhaWxhYmxlOgoKYGBge3J9CmRiTGlzdFRhYmxlcyhjb24pCmBgYAoKTGV0J3MgbG9vayBhdCBvbmUgb2YgdGhlIHRhYmxlcywgdGhlIGxpc3Qgb2YgY292ZXIgY3JvcCBzcGVjaWVzOgoKYGBge3IgcGFnZWQucHJpbnQgPSBGQUxTRX0KZGJSZWFkVGFibGUoY29uLCAiY2Nfc3BlY2llcyIpCmBgYAoKTm90aWNlIHRoYXQgaXQgcmV0dXJuZWQgdGhlIHdob2xlIHRhYmxlLCBzaW5jZSB0aGlzIHdhcyBub3QgYSBsYXp5IHF1ZXJ5LiBGb3IgYSBzbWFsbCB0YWJsZSBsaWtlIHRoaXMsIHRoYXQncyBub3QgYSBwcm9ibGVtLiBCdXQgc29tZSBvZiB0aGUgdGFibGVzIGFyZSAqKm1hbnkqKiByb3dzLiBJZiB5b3UgdHJ5IHRvIHB1bGwgdGhlbSBhbGwgaW4gdXNpbmcgKipgZGJSZWFkVGFibGVgKiosIHlvdSdsbCBlbmQgdXAgd2FpdGluZyBhIHdoaWxlIGZvciBzb21lIHRhYmxlcy4KCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CiMgVGFrZXMgfjExcyB0byByZXR1cm4KZGJMaXN0VGFibGVzKGNvbikgJT4lIAogIHN0cmluZ3I6OnN0cl9zdWJzZXQoIl5wZyIsIG5lZ2F0ZSA9IFRSVUUpICU+JSAKICBwdXJycjo6c2V0X25hbWVzKCkgJT4lIAogIHB1cnJyOjptYXBfZGZyKAogICAgfnRibChjb24sIC54KSAlPiUgCiAgICAgIHRhbGx5KCkgJT4lIAogICAgICBjb2xsZWN0KCksCiAgICAuaWQgPSAidGFibGUiCiAgICApICU+JQogIHByaW50KG4gPSBJbmYpCmBgYAoKVGhhdCdzIGEgaGVscGZ1bCBsaXN0LCBidXQgdGhhdCB3YXMgYWxzbyBhIG1vdXRoZnVsIG9mIGNvZGUuIExldCdzIGJyZWFrIGl0IGRvd24gYSBiaXQuIFRoZSBmb2xsb3dpbmcgaXMgaWRlbnRpY2FsIGNvZGUsIGJ1dCB3cml0dGVuIHdpdGggaW50ZXJtZWRpYXRlIHZhcmlhYmxlcyBpbnN0ZWFkIG9mIGNoYWluaW5nIHdpdGggdGhlICoqYCU+JWAqKiAoKnBpcGUqKSBvcGVyYXRvci4gSWYgeW91J3JlIGZhbWlsaWFyIHdpdGggYmFzZSBSIGluc3RlYWQsIHlvdSBjb3VsZCB1c2UgKipgbGFwcGx5YCoqIGluc3RlYWQgb2YgKipgcHVycnI6Om1hcF8qYCoqLCBidXQgYHtwdXJycn1gIGhhbmRsZXMgYSBsb3Qgb2YgZGV0YWlscyBsaWtlIG91dHB1dCB0eXBlIGZvciB5b3UuIFlvdSBkb24ndCBuZWVkIHRvIGtub3cgYHtwdXJycn1gIGZvciB0aGUgcmVzdCBvZiB0aGlzIHR1dG9yaWFsLCBidXQgaWYgeW91IHdyaXRlIGEgbG90IG9mIGxvb3BzLCBpdCB3aWxsIGltcHJvdmUgeW91ciBjb2RlLiBUaGUgb3V0cHV0IGlzbid0IHNob3duIGJlbG93LCBzaW5jZSBpdCdzIHRoZSBzYW1lIGFzIGFib3ZlLgoKYGBge3IgZXZhbCA9IEZBTFNFLCBwYWdlZC5wcmludCA9IEZBTFNFfQojIGdldCB0aGUgbGlzdCBvZiB0YWJsZXMgYXMgYSB2ZWN0b3IKdGFibGVzIDwtIGRiTGlzdFRhYmxlcyhjb24pCnRhYmxlcwoKIyBkaXNjYXJkIHRoZSB0YWJsZXMgdGhhdCBiZWdpbiB3aXRoICJwZyIKY2xlYW5fdGFibGVzIDwtIHN0cmluZ3I6OnN0cl9zdWJzZXQodGFibGVzLCAiXnBnIiwgbmVnYXRlID0gVFJVRSkKY2xlYW5fdGFibGVzCgojIG5hbWUgdGhlIHZlY3RvciBzbyB3ZSBjYW4gaWRlbnRpZnkgZWFjaCByb3cgbGF0ZXIKY2xlYW5fdGFibGVzIDwtIHB1cnJyOjpzZXRfbmFtZXMoY2xlYW5fdGFibGVzKQpjbGVhbl90YWJsZXMKCiMgaGVscGVyIGZ1bmN0aW9uIHRvIGNvdW50IHJvd3MgYXMgYSBsYXp5IHF1ZXJ5Cm5yb3dzX3JlbW90ZSA8LSBmdW5jdGlvbihuYW1lKSB7CiAgCiAgcmVtb3RlX3RhYmxlIDwtIHRibChjb24sIG5hbWUpICAgICAgICMgbGF6aWx5IHB1bGwgdGhlIHRhYmxlCiAgCiAgcmVtb3RlX2NvdW50IDwtIHRhbGx5KHJlbW90ZV90YWJsZSkgICMgY291bnQgdGhlIHJvd3Mgb24gdGhlIERCIHNpZGUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjICAgbm90IGxvY2FsbHkKICAKICBjb2xsZWN0KHJlbW90ZV9jb3VudCkgICAgICAgICAgICAgICAgIyBwdWxsIHRoZSBjb3VudCBpbiB0byB5b3VyIGxvY2FsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjICAgUiBlbnZpcm9ubWVudAp9CgojIGxvb3Agb3ZlciBlYWNoIHRhYmxlIGFuZCBhcHBseSB0aGUgaGVscGVyIGZ1bmN0aW9uLCAKIyAgIHJvdy1iaW5kaW5nIHRoZSByZXN1bHRzIHRvIGEgc2luZ2xlIHRpYmJsZS9kYXRhZnJhbWUKcHVycnI6Om1hcF9kZnIoCiAgY2xlYW5fdGFibGVzCiAgfm5yb3dzX3JlbW90ZSgueCksCiAgLmlkID0gInRhYmxlIgogICkKCiMgYHByaW50KC4uLiwgbj1JbmYpYCBqdXN0IG1ha2VzIHN1cmUgaXQgcHJpbnRzIAojICAgYWxsIHRoZSByb3dzIHRvIHRoZSBvdXRwdXQKYGBgCgojIE1ha2Ugc29tZSBzaW1wbGUgcXVlcmllcwoKT2theSwgbm93IHRoYXQgd2UncmUgc2V0IHVwIGFuZCB3ZSBrbm93IHNvbWUgcGl0ZmFsbHMgdG8gd2F0Y2ggb3V0IGZvciwgbGV0J3MgdHJ5IGEgcXVlcnkgdG8gbG9vayBhdCB0aGUgc2l0ZXMgZW5yb2xsZWQuIFJlbWVtYmVyLCBzaW5jZSBpdCdzIGEgbGF6eSBxdWVyeSwgd2UgZG9uJ3Qga25vdyBob3cgbWFueSByb3dzIHRoZXJlIGFyZSB1bnRpbCB3ZSAqKmBjb2xsZWN0KClgKiogaXQuCgpgYGB7ciBwYWdlZC5wcmludCA9IEZBTFNFfQp0YmwoY29uLCAic2l0ZV9pbmZvcm1hdGlvbiIpCmBgYAoKUmVtZW1iZXIgdGhhdCB0aGlzIGlzIGlkZW50aWNhbCB0byBwcmV2aWV3aW5nIGBTRUxFQ1QgKiBGUk9NIHNpdGVfaW5mb3JtYXRpb25gIGluIHZhbmlsbGEgU1FMLiBMZXQncyBzZWUgaG93IG1hbnkgc2l0ZXMgd2VyZSBlbnJvbGxlZCBpbiBlYWNoIHllYXI6CgpgYGB7ciBwYWdlZC5wcmludCA9IEZBTFNFfQp5ZWFyX2RhdGEgPC0gdGJsKGNvbiwgInNpdGVfaW5mb3JtYXRpb24iKSAlPiUgCiAgYXJyYW5nZSh5ZWFyKSAlPiUgCiAgZ3JvdXBfYnkoeWVhcikgJT4lIAogIHRhbGx5KCkKCnllYXJfZGF0YQoKc2hvd19xdWVyeSh5ZWFyX2RhdGEpCmBgYAoKV2hhdCBhYm91dCBpZiB3ZSB3YW50ZWQgdG8gc3RvcmUgYSBsaXN0IG9mIHRoZSBzaXRlIGNvZGVzIGFuZCB3aGF0IHN0YXRlIGFuZCB5ZWFyIHRoZXkgd2VyZSBlbnJvbGxlZCBpbj8KCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CnNpdGVfbGlzdCA8LSB0YmwoY29uLCAic2l0ZV9pbmZvcm1hdGlvbiIpICU+JSAKICBzZWxlY3QoeWVhciwgc3RhdGUsIGNvZGUsIGxhdGl0dWRlLCBsb25naXR1ZGUpICU+JSAKICBhcnJhbmdlKHllYXIsIHN0YXRlLCBjb2RlKSAlPiUgCiAgY29sbGVjdCgpCgpzaXRlX2xpc3QKYGBgCgpOb3cgeW91IGNvdWxkIGRvIGFueSBsb2NhbCBjb21wdXRhdGlvbiBvciBtYW5pcHVsYXRpb24geW91IHdhbnRlZC4gTXkgcnVsZSBvZiB0aHVtYiBpcyB0aGF0IGlmIGl0J3Mgb25lIG9mIHRoZSBjb21tb24gb3BlcmF0aW9ucyAoKipgc2VsZWN0YCoqaW5nIGNvbHVtbnMsICoqYGFycmFuZ2VgKippbmcgdG8gc29ydCwgKipgZmlsdGVyYCoqaW5nIHJvd3MsICoqYCpfam9pbmAqKmluZyB0d28gdGFibGVzKSwgZ28gYWhlYWQgYW5kIGRvIHRob3NlIHdpdGggbGF6eSBxdWVyaWVzLiBJZiB5b3UgaGF2ZSBtb3JlIGNvbXBsZXggb3BlcmF0aW9ucyB5b3Ugd2FudCB0byBkbywgaXQgbWlnaHQgYmUgYmV0dGVyIHRvIHB1bGwgdGhlIHRhYmxlIGluIGxvY2FsbHkgd2l0aCAqKmBjb2xsZWN0KClgKiouIEFnYWluLCBpdCdzIGEgZ29vZCBpZGVhIHRvIGNoZWNrIGhvdyBtYW55IHJvd3Mgd2lsbCBiZSByZXR1cm5lZCBiZWZvcmUgeW91IGRvIHRoYXQgdGhvdWdoOgoKYGBge3IgcGFnZWQucHJpbnQgPSBGQUxTRX0KdGJsKGNvbiwgInNpdGVfaW5mb3JtYXRpb24iKSAlPiUgCiAgc2VsZWN0KHllYXIsIHN0YXRlLCBjb2RlLCBsYXRpdHVkZSwgbG9uZ2l0dWRlKSAlPiUgCiAgYXJyYW5nZSh5ZWFyLCBzdGF0ZSwgY29kZSkgJT4lIAogIHRhbGx5KCkKYGBgCgojIyBNYXBwaW5nIHRoZSBzaXRlcwoKU28gbGV0J3MgZG8gc29tZSBsb2NhbCBjb21wdXRhdGlvbiBvbiB0aGUgKipgc2l0ZV9saXN0YCoqIG9iamVjdCB3ZSBzdG9yZWQgYWJvdmUuIEZpcnN0IHdlJ2xsIG1ha2UgYSBzdGF0aWMgbWFwIHVzaW5nIHRoZSBge2dncGxvdDJ9YCBwYWNrYWdlLCB3aGljaCBpcyBnb29kIGZvciBtYWtpbmcgaGlnaCBxdWFsaXR5IGN1c3RvbWl6YWJsZSBpbWFnZXMuIFRoZW4gd2UnbGwgbWFrZSBhbiBpbnRlcmFjdGl2ZSBtYXAgd2l0aCBge2xlYWZsZXR9YCwgYSBKYXZhU2NyaXB0IHBhY2thZ2UgdGhhdCBlbWJlZHMgbWFwcyBpbiBkb2N1bWVudHMgbGlrZSB0aGVzZSBvciB3ZWIgYXBwbGljYXRpb25zLiAKCldlIHdvbid0IGZ1c3MgYXJvdW5kIHRvbyBtdWNoIHdpdGggdGhlIG9wdGlvbnMgaW4gZWl0aGVyLCBidXQgYm90aCBhcmUgcG93ZXJmdWwgZW5vdWdoIHRvIGRvIGFueSBraW5kIG9mIG1hcHBpbmcgdmlzdWFsaXphdGlvbiB5b3UgY2FuIGltYWdpbmUuIFlvdSBkb24ndCBuZWVkIGFueSBvZiB0aGUgb3RoZXIgcGFja2FnZXMgSSdtIHVzaW5nIGhlcmUgaWYgeW91IGFyZW4ndCBtYWtpbmcgbWFwcywgYnV0IHRoZXkncmUgaGFuZHkgdG8gaGF2ZSBpZiB5b3UgYXJlLgoKYGBge3IgcGFnZWQucHJpbnQgPSBGQUxTRX0Kc3RhdGVfb3V0bGluZXMgPC0gCiAgcm5hdHVyYWxlYXJ0aGRhdGE6OnN0YXRlczUwICU+JSAKICBzZjo6c3RfYXNfc2YoKSAlPiUgCiAgZmlsdGVyKHNyX2FkbTBfYTMgPT0gIlVTQSIpCgpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gc3RhdGVfb3V0bGluZXMpICsKICBnZW9tX3BvaW50KAogICAgZGF0YSA9IHNpdGVfbGlzdCwgCiAgICBhZXMobG9uZ2l0dWRlLCBsYXRpdHVkZSwgY29sb3IgPSB5ZWFyKQogICAgKSArCiAgY29vcmRfc2YoCiAgICB4bGltID0gcmFuZ2Uoc2l0ZV9saXN0JGxvbmdpdHVkZSwgbmEucm0gPSBUUlVFKSwKICAgIHlsaW0gPSByYW5nZShzaXRlX2xpc3QkbGF0aXR1ZGUsIG5hLnJtID0gVFJVRSkKICApCgpsaWJyYXJ5KGxlYWZsZXQpCmxlYWZsZXQoCiAgc2l0ZV9saXN0LAogIG9wdGlvbnMgPSBsZWFmbGV0T3B0aW9ucyhtYXhab29tID0gNikKICApICU+JSAKICBhZGRUaWxlcygpICU+JSAKICBhZGRNYXJrZXJzKAogICAgfnJvdW5kKGxvbmdpdHVkZSwgMSksCiAgICB+cm91bmQobGF0aXR1ZGUsIDEpCiAgKQpgYGAKCgojIyBSZWR1Y2luZyByb3dzIHJldHVybmVkCgpXaXRoIHRoZSBtYXBwaW5nIGV4YW1wbGUgd2Ugd2FudGVkIGFsbCB0aGUgc2l0ZXMsIGJ1dCBzb21lIHRhYmxlcyBqdXN0IGhhdmUgYSBsb3Qgb2Ygcm93cy4gWW91IHByb2JhYmx5IHdhbnQgdG8gdXNlIGEgc3Vic2V0IG9mIHRob3NlIHJvd3MgdG8gc3RhcnQgeW91ciBtb2RlbCBvciB2aXN1YWxpemUgYmVmb3JlIGp1bXBpbmcgaW4gd2l0aCB0aGUgd2hvbGUgdGhpbmcuCgpXZSBzYXcgYWJvdmUgdGhhdCB0aGUgbGFyZ2VzdCB0YWJsZSBpbiB0aGUgREIgaXMgdGhlICoqYCJ3ZWF0aGVyImAqKiB0YWJsZS4gTGV0J3MgbG9vayBhdCBqdXN0IHRoZSBmaXJzdCBmZXcgcm93cyB3aXRoIGEgbGF6eSBxdWVyeToKCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CnRibChjb24sICJ3ZWF0aGVyIikKYGBgCgpXZSBsZWFybmVkIGluIHRoZSBsYXN0IG5vdGVib29rIG9uIFNRTGl0ZSBob3cgdG8gZmlsdGVyIG9uIHRleHQgYW5kIG51bWVyaWMgY29sdW1ucywgYnV0IHRoZXJlJ3MgdHdvIG5ldyBvbmVzIGhlcmUsICoqYGRhdGVgKiogYW5kICoqYGRhdGV0aW1lYCoqLiBSZWFsbHkgdGhlc2UgYXJlIHN0aWxsIGJvdGgganVzdCBudW1iZXJzLCB1c3VhbGx5IGNvdW50aW5nIHNvbWV0aGluZyBsaWtlIHRoZSBudW1iZXIgb2Ygc2Vjb25kcyBzaW5jZSAxOTcwLUphbi0wMSwgb3Igc29tZXRoaW5nIGxpa2UgdGhhdC4gQnV0IGl0J3MgaGFyZCB0byB0aGluayBpbiB0ZXJtcyBvZiB3aGVuIGAxODI2MmAgaXMsIG9yIGAxNTc3ODU0ODAwYC4gWW91J3JlIG11Y2ggbW9yZSBmYW1pbGlhciB3aXRoIHdvcmtpbmcgd2l0aCAqKkphbnVhcnkgMSwgMjAyMCoqIGFuZCAqKjIwMjAtMDEtMDEgMTI6MDBBTSBFU1QqKi4KClRoZXJlIGFyZSB0d28gbWFpbiB3YXlzIHRvIHdvcmsgd2l0aCBkYXRlKHRpbWUpcyBpbiBSLiBJbiBiYXNlIFIsIHRoZXJlJ3MgKipgYXMuRGF0ZWAqKiBhbmQgKipgYXMuUE9TSVhjdGAqKiAoYnV0IHRoZSBkb2N1bWVudGF0aW9uIHRvIHVzZSB0aGUgZm9ybWF0cyBpcyBpbiAqKmA/c3RycHRpbWVgKiopIGFuZCBpbiB0aGUgYHt0aWR5dmVyc2V9YCwgdGhlcmUncyBhIHdob2xlIHBhY2thZ2UgY2FsbGVkIGB7bHVicmlkYXRlfWAuIElmIHRoYXQgc2VlbXMgdG9vIGNvbXBsaWNhdGVkLCB5b3UncmUgcmlnaHQuIERhdGVzIGFuZCB0aW1lcyBbYXJlIG5vdG9yaW91c10oaHR0cHM6Ly94a2NkLmNvbS8xMTc5Lyl7dGFyZ2V0PSJfYmxhbmsifSBmb3IgYmVpbmcgYSBiZWFyIGluIGV2ZXJ5IHByb2dyYW1taW5nIGxhbmd1YWdlIGFuZCBvbiBldmVyeSBjb21wdXRlciBwbGF0Zm9ybS4gSXQncyBlYXNpZXN0IGlmIHlvdSB1c2UgYW4gdW5hbWJpZ3VvdXMgaW50ZXJuYXRpb25hbCBzdGFuZGFyZCwgbGlrZSBbSVNPLTg2MDFdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0lTT184NjAxKXt0YWdldD0iX2JsYW5rIn0sIFtmdXJ0aGVyIHJlYWRpbmddKGh0dHBzOi8vd3d3LmNsLmNhbS5hYy51ay9+bWdrMjUvaXNvLXRpbWUuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifS4KCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CmR0cyA8LSBjKCIxOTg0LTA4LTIzIiwgIjIwMjAtMDEtMDEiKQp0bXMgPC0gYygiMTk4NC0wOC0yMyA3OjMwOjAwIFBNIiwgIjIwMjAtMDEtMDEgMTI6MDA6MDAgQU0iKQppc28gPC0gYygiMTk4NC0wOC0yMyAxOTozMDowMCIsICAgIjIwMjAtMDEtMDEgMDA6MDA6MDAiKQoKZGF0YS5mcmFtZSgKICBkYXRlcyA9IGFzLkRhdGUoZHRzKSwKICB0aW1lcyA9IGFzLlBPU0lYY3QodG1zLCBmb3JtYXQgPSAiJVktJW0tJWQgJUk6JU06JVMgJXAiKSwKICBpc28gPSBhcy5QT1NJWGN0KGlzbykKKQoKdGliYmxlKAogIGRhdGVzID0gbHVicmlkYXRlOjphc19kYXRlKGR0cyksCiAgdGltZXMgPSBsdWJyaWRhdGU6OmFzX2RhdGV0aW1lKHRtcyksCiAgaXNvID0gbHVicmlkYXRlOjphc19kYXRldGltZShpc28pCikKYGBgCgpOb3RpY2UgdGhhdCB1c2luZyB0aGUgc3RhbmRhcmQgZm9ybWF0IGluIHlvdXIgdGV4dCBpbnB1dCBtYWtlcyB5b3VyIGNvZGUgYSBsb3Qgc2ltcGxlciB0byByZWFkLiBMaWtld2lzZSwgYHtsdWJyaWRhdGV9YCBpcyBwcmV0dHkgZ29vZCBhdCBndWVzc2luZywgc28gaXQgY2FuIGJlIGEgaGFuZHkgc2hvcnRjdXQgYXMgd2VsbC4gSSdsbCBzdGljayB3aXRoIGJhc2UgUiBmb3IgdGhpcyBleGFtcGxlIHRob3VnaCwgYnV0IEkgZ2VuZXJhbGx5IHByZWZlciB0aGUgb3RoZXIgcGFja2FnZS4KClNvIGxldCdzIHNheSBpbnN0ZWFkIG9mIHRoZSBtaWxsaW9uIG9yIHNvIHJvd3MgdGhhdCAqKmAid2VhdGhlciJgKiogaGFzLCB3ZSB3YW50IGp1c3QgdGhlIG9ic2VydmF0aW9ucyBmcm9tIEp1bHkgNCwgMjAxOC4gSWYgd2UncmUgdXNpbmcgdGhlICoqYGRhdGVgKiogY29sdW1uLCB3ZSB3b3VsZCBuZWVkIHRvIHNlbGVjdCBleGFjdCBlcXVhbGl0eS4gQnV0IGlmIHdlJ3JlIHVzaW5nIHRoZSAqKmB0aW1lemAqKiBjb2x1bW4sIHdlIHdhbnQgdG8gc2VsZWN0IGEgcmFuZ2U6IGV2ZXJ5dGhpbmcgZ3JlYXRlciB0aGFuIG1pZG5pZ2h0IHRoYXQgbW9ybmluZyBhbmQgbGVzcyB0aGFuIDExOjU5OjU5UE0gdGhhdCBuaWdodC4KCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CmRhdGVfcXVlcnkgPC0gdGJsKGNvbiwgIndlYXRoZXIiKSAlPiUgCiAgZmlsdGVyKGRhdGUgPT0gYXMuRGF0ZSgiMjAxOC0wNy0wNCIpKQoKZGF0ZV9xdWVyeQoKc2hvd19xdWVyeShkYXRlX3F1ZXJ5KQoKZGF0ZV9xdWVyeSAlPiUgCiAgZ3JvdXBfYnkoY29kZSkgJT4lIAogIHRhbGx5KCkKYGBgCgpTbyB3ZSBjYW4gc2VlIHRoYXQgMjQgaG91cnMgb2Ygb2JzZXJ2YXRpb25zIGFyZSByZXR1cm5lZCBpbiBvdXIgcXVlcnkgZm9yIGVhY2ggc2l0ZS4gTGV0J3MgdHJ5IGl0IHdpdGggdGltZXMgaW5zdGVhZDoKCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CnRfc3RhcnQgPC0gYXMuUE9TSVhjdCgiMjAxOC0wNy0wNCAwMDowMDowMCIpCnRfZW5kIDwtIGFzLlBPU0lYY3QoIjIwMTgtMDctMDQgMjM6NTk6NTkiKQoKdGltZV9xdWVyeSA8LSB0YmwoY29uLCAid2VhdGhlciIpICU+JSAKICBmaWx0ZXIoYmV0d2Vlbih0aW1leiwgdF9zdGFydCwgdF9lbmQpKQoKdGltZV9xdWVyeQoKc2hvd19xdWVyeSh0aW1lX3F1ZXJ5KQoKdGltZV9xdWVyeSAlPiUgCiAgZ3JvdXBfYnkoY29kZSkgJT4lIAogIHRhbGx5KCkKYGBgCgpXZSBnZXQgKiphbG1vc3QqKiB0aGUgc2FtZSByZXN1bHRzLiBOb3RlIHRoYXQgaW5zdGVhZCBvZiAqKmBkcGx5cjo6YmV0d2VlbmAqKiwgd2UgY291bGQgaGF2ZSBzYWlkIGBmaWx0ZXIodGltZXogPj0gdF9zdGFydCwgdGltZXogPD0gdF9lbmQpYCwgYW5kIGl0IHdvdWxkIGhhdmUgYmVlbiB0aGUgc2FtZS4gU29tZXRpbWVzIEkgcHJlZmVyIHRoZSBsb25nLXdpbmRlZCB3YXkgb2Ygd3JpdGluZyBpdCwgaWYgaXQncyBjbGVhcmVyIHRvIHJlYWQgdGhlIGNvZGUgbGF0ZXIuCgo+ICoqV0FSTklORzoqKiBZb3UgaGF2ZSB0byB3YXRjaCB5b3VyIHRpbWV6b25lcyBjYXJlZnVsbHkuIFlvdSdsbCBub3RpY2UgaW4gdGhlIHRyYW5zbGF0ZWQgU1FMLCBpdCB1c2VkIGAnMjAxOC0wNy0wNFQwNDowMDowMFonYCBhcyBvdXIgc3RhcnQgdGltZS4gSSBhbSB3cml0aW5nIHRoaXMgY29kZSBvbiBhIG1hY2hpbmUgaW4gdGhlIGBBbWVyaWNhL05ld19Zb3JrYCB0aW1lIHpvbmUsIGFuZCBvbiBKdWx5IDQgMjAxOCwgdGhhdCB3YXMgZXF1aXZhbGVudCB0byBhIGAtNGAgb2Zmc2V0IChiZWNhdXNlIG9mIERheWxpZ2h0IFNhdmluZyBUaW1lLCBpdCdzIGAtNWAgdGhlIHJlc3Qgb2YgdGhlIHllYXIpLiBUaHVzIFIgYXNzdW1lZCB0aGF0IGFueSB0aW1lIEkgcHV0IGluIHdpdGhvdXQgYW4gZXhwbGljaXQgdGltZXpvbmUgaXMgbXkgbG9jYWwgdGltZSwgd2hpbGUgdGhlICoqYHRpbWV6YCoqIGNvbHVtbiBpbiB0aGUgREIgaXMgVVRDIChvciAiWnVsdSIpIHRpbWUuCgo+ICoqTk9URToqKiBXaGV0aGVyIHRoZSBmaXJzdCBxdWVyeSAodGhlIHdob2xlIG9mIDIwMTgtMDctMDQgYXQgdGhlIHByaW1lIG1lcmlkaWFuKSBvciB0aGUgc2Vjb25kIHF1ZXJ5IChiZXR3ZWVuIG1pZG5pZ2h0IGFuZCBtaWRuaWdodCBvbiAyMDE4LTA3LTA0IG9uIHRoZSBlYXN0IGNvYXN0KSBpcyB3aGF0IHlvdSB3YW50IGlzIHVwIHRvIHlvdS4gSnVzdCBiZSBhd2FyZSBvZiB3aGF0IHRpbWVzIHlvdSdyZSBhY3R1YWxseSBhc2tpbmcgZm9yIGFuZCBnZXR0aW5nIGluIHJldHVybi4KCgojIEpvaW5pbmcgdHdvIHRhYmxlcwoKV2UgYWxyZWFkeSBleHBsb3JlZCB0aGUgbW9zdCBpbXBvcnRhbnQgdGFibGUsICoqYCJzaXRlX2luZm9ybWF0aW9uImAqKi4gSXQncyBpbXBvcnRhbnQsIGJlY2F1c2UgaXQga2V5cyB0aGUgdGhyZWUgbGV0dGVyIGZhcm0gY29kZXMgdG8gYWxsIHRoZSBvdGhlciBkYXRhIGFib3V0IGVhY2ggZmFybS4gV2hhdCBhYm91dCAqKmAiZGVjb21wX2Jpb21hc3MiYCoqPyBUaGF0J3MgYWxsIHRoZSBkYXRhIGFib3V0IHRoZSBsaXR0ZXJiYWcgZGVjb21wb3NpdGlvbiBzdHVkeSwgYW5kIGl0J3Mgd2hlcmUgZHJ5IHdlaWdodCBvZiB0aGUgY292ZXIgY3JvcCBiaW9tYXNzIGlzIHN0b3JlZC4KCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CnRibChjb24sICJkZWNvbXBfYmlvbWFzcyIpCmBgYAoKQnV0IGl0IG9ubHkgaGFzIHRoZSBmYXJtIGNvZGVzLCBzbyB3ZSBuZWVkIHRvIGpvaW4gaXQgd2l0aCB0aGUgc2l0ZSBpbmZvcm1hdGlvbiB0YWJsZS4gV2hhdCBraW5kIG9mIGpvaW4gaXMgYXBwcm9wcmlhdGU/IEluIHRoZSBkZWNvbXAgZGF0YSwgdGhlcmUgYXJlIG11bHRpcGxlIHJvd3MgcGVyIGZhcm0gY29kZSAoMjQgaWYgbm8gYmFncyBnb3QgZGVzdHJveWVkKSwgYW5kIGZhcm0gY29kZSBpcyB0aGUgb25seSBrZXkgdGhhdCBjb25uZWN0cyB0aGUgdHdvIHRhYmxlcy4gVGhpcyBpcyBjYWxsZWQgYSBvbmUtdG8tbWFueSBjb3JyZXNwb25kZW5jZS4KCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CnRibChjb24sICJkZWNvbXBfYmlvbWFzcyIpICU+JSAKICBncm91cF9ieShjb2RlKSAlPiUgCiAgdGFsbHkoKQoKdGJsKGNvbiwgInNpdGVfaW5mb3JtYXRpb24iKSAlPiUgCiAgZ3JvdXBfYnkoY29kZSkgJT4lIAogIHRhbGx5KCkKYGBgCgojIyBKb2luIHR5cGVzCgpBc3N1bWluZyB3ZSBqb2luIHRoZW0gd2l0aCAqKmAic2l0ZV9pbmZvcm1hdGlvbiJgKiogYXMgdGhlICJsZWZ0IiB0YWJsZSBhbmQgKipgImRlY29tcF9iaW9tYXNzImAqKiBhcyB0aGUgInJpZ2h0IiB0YWJsZToKCiogQSAqKmBsZWZ0X2pvaW4oKWAqKiB3b3VsZCByZXR1cm4gCiAgKyBhbGwgdGhlIGNvbHVtbnMgaW4gYm90aCB0YWJsZXMKICArIG9ubHkgT05FIHJvdyBwZXIgY29kZQoqIEEgKipgcmlnaHRfam9pbmAqKiB3b3VsZCByZXR1cm4gCiAgKyBhbGwgdGhlIGNvbHVtbnMgaW4gYm90aCB0YWJsZXMKICArIGFsbCB0aGUgcm93cyBmcm9tIHRoZSBkZWNvbXAgdGFibGUKICAgICsgaW5jbHVkaW5nIHJvd3Mgd2l0aCAqKm5vIG1hdGNoKiogaW4gdGhlIHNpdGUgaW5mbyB0YWJsZSAoYE5BYHMgZm9yIGxhdC9sb25nIGV0YykKKiBBbiAqKmBpbm5lcl9qb2luKClgKiogd291bGQgcmV0dXJuCiAgKyBhbGwgdGhlIGNvbHVtbnMgaW4gYm90aCB0YWJsZXMKICArIE1BVENISU5HIHJvd3MgZnJvbSBib3RoIHRhYmxlcyAKICAgICsgKG5vIGBOQWBzKQoqIEFuICoqYG91dGVyX2pvaW4oKWAqKiB3b3VsZCByZXR1cm4KICArIGFsbCB0aGUgY29sdW1ucyBpbiBib3RoIHRhYmxlcwogICsgQUxMIHJvd3MgZnJvbSBib3RoIHRhYmxlcyAKICAgICsgKGBOQWBzIGluIGxhdC9sb25nICYgYXMgYE5BYHMgaW4gZHJ5IHdlaWdodCkKKiBBICoqYHNlbWlfam9pbmAqKiB3b3VsZCByZXR1cm4KICArIG9ubHkgY29sdW1ucyBmcm9tIHNpdGUgaW5mbwogICsgb25lIHJvdyBwZXIgY29kZSBpbiB0aGUgc2l0ZSBpbmZvIHRhYmxlIAogICAgKyAodGhlIGZpcnN0IG1hdGNoIGZyb20gZGVjb21wIHRhYmxlLCBubyBgTkFgcykKKiBBbiAqKmBhbnRpX2pvaW5gKiogd291bGQgcmV0dXJuCiAgKyBvbmx5IGNvbHVtbnMgZnJvbSBzaXRlIGluZm8KICArIE9ORSByb3cgcGVyIGNvZGUgd2hlcmUgdGhlcmUncyBOTyBNQVRDSCBpbiBkZWNvbXAgZGF0YQogICsgICAodXNlZnVsIGZvciBmaW5kaW5nIGFsbCB0aGUgbWlzbWF0Y2hlcy9gTkFgcykKCkkgYWxtb3N0IGFsd2F5cyB3YW50IGEgKipgZnVsbF9qb2luYCoqLCBzbyBJIGNhbiBtYWtlIHN1cmUgSSBjYXRjaCBhbnkgbWlzbWF0Y2hlcyBhbmQgaW52ZXN0aWdhdGUgdGhlbS4gQSAqKmByaWdodF9qb2luYCoqIGlzIGFsc28gZ29vZCBmb3IgIm9uZS10by1tYW55IiBjb3JyZXNwb25kZW5jZXMsIGFzIHdlbGwgYXMgYW4gKipgaW5uZXJfam9pbmAqKi4gQmV3YXJlIG9mIHRoZSBkcm9wcGVkIG9ic2VydmF0aW9ucyB3aXRoIGlubmVyIGpvaW5zOyBob3dldmVyLCB0aG9zZSByb3dzIHdvdWxkIGJlICJ1bmlkZW50aWZpYWJsZSIgYmVjYXVzZSBvZiBtaXNzaW5nIGRhdGEsIHNvIHlvdSdkIGVuZCB1cCBkcm9wcGluZyB0aGVtIGluIHlvdXIgYW5hbHlzaXMgYW55d2F5LiBJdCdzIGp1c3Qgc29tZXRoaW5nIHRvIGtlZXAgaW4gbWluZC4KCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CmNvbG5hbWVzKHRibChjb24sICJzaXRlX2luZm9ybWF0aW9uIikpCmNvbG5hbWVzKHRibChjb24sICJkZWNvbXBfYmlvbWFzcyIpKQpgYGAKCj4gKipXQVJOSU5HOioqIEFsd2F5cyBsb29rIGF0IHdoYXQgY29sdW1ucyBqb2lucyBhcmUgbWF0Y2hpbmcgb24gaWYgeW91J3JlIG5vdCBzcGVjaWZ5aW5nIHRoZW0gbWFudWFsbHkuIEZvciBleGFtcGxlLCBib3RoIG9mIHRoZXNlIHRhYmxlcyBzaGFyZSB0aGUgKipgY29kZWAqKiBjb2x1bW4gKHdoaWNoIHdlIHdhbnQpIGFzIHdlbGwgYXMgdGhlICoqYGNpZGAqKiBjb2x1bW4gKHdoaWNoIGlzIGFuIGludGVybmFsIERCIGlkZW50aWZpZXIpLiBJZiB3ZSBsZXQgaXQgam9pbiBvbiB0aGUgKipgY2lkYCoqIGNvbHVtbiwgd2UncmUgZ29ubmEgaGF2ZSB0cm91YmxlLCBzaW5jZSB0aGV5IHdvbid0IGV2ZXIgbWF0Y2guCgpgYGB7ciBwYWdlZC5wcmludCA9IEZBTFNFfQppbm5lcl9qb2luKAogIHRibChjb24sICJzaXRlX2luZm9ybWF0aW9uIiksCiAgdGJsKGNvbiwgImRlY29tcF9iaW9tYXNzIiksCiAgYnkgPSAiY29kZSIgICAgICAgIyBmb3JjZSB1c2luZyBvbmx5IHRoZSBgY29kZWAgY29sLiBmb3IgbWF0Y2hpbmcKKQpgYGAKCk5vdGljZSB0aGF0IG5vdyB0aGVyZSdzICoqYGNpZC54YCoqIGFuZCAqKmBjaWQueWAqKiwgd2hpY2ggYXJlIGZyb20gdGhlIGxlZnQgYW5kIHJpZ2h0IHRhYmxlcyByZXNwZWN0aXZlbHkgYW5kIGRvbid0IG1hdGNoIGVhY2ggb3RoZXIuIFRoaXMgcXVlcnkgcmV0dXJucyB3YXkgdG9vIG1hbnkgY29sdW1ucywgc28gbGV0J3Mgc2ltcGxpZnkgdGhlIGRhdGEgc28gaXQncyBlYXNpZXIgdG8gbG9vayBhdDoKCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CmJhZ19kYXRhIDwtIGlubmVyX2pvaW4oCiAgdGJsKGNvbiwgInNpdGVfaW5mb3JtYXRpb24iKSwKICB0YmwoY29uLCAiZGVjb21wX2Jpb21hc3MiKSwKICBieSA9ICJjb2RlIgopICU+JSAKICBzZWxlY3QoCiAgICBjb2RlLCB5ZWFyLCBzdGF0ZSwgICAgICAgICAgIyB2YXJzIHRvIGlkZW50aWZ5IHRoZSBzaXRlCiAgICBzdWJwbG90LCBzdWJzYW1wbGUsIHRpbWUsICAgIyB2YXJzIHRvIGlkZW50aWZ5IHRoZSBiYWcKICAgIGRyeV9iaW9tYXNzX3d0LCBwZXJjZW50X24gICAjIGFjdHVhbCBkYXRhIHdlIHdhbnQKICAgICkKCnNob3dfcXVlcnkoYmFnX2RhdGEpCgpiYWdfZGF0YQpgYGAKClRoYXQgbmFzdHkgU1FMIHN0YXRlbWVudCBjb3VsZCBiZSBzaW1wbGlmaWVkIHRvOgoKYGBge3IgZXZhbCA9IEZBTFNFLCBwYWdlZC5wcmludCA9IEZBTFNFfQpkYkdldFF1ZXJ5KGNvbiwgCiAgIgogIFNFTEVDVCBMSFMuY29kZSwgeWVhciwgc3RhdGUsIHRpbWUsIGRyeV9iaW9tYXNzX3d0LCBwZXJjZW50X24KICAgIEZST00gc2l0ZV9pbmZvcm1hdGlvbiBBUyBMSFMKICAgIElOTkVSIEpPSU4gZGVjb21wX2Jpb21hc3MgQVMgUkhTCiAgICBPTiAoTEhTLmNvZGUgPSBSSFMuY29kZSkKICAiKQpgYGAKCkJ1dCBhZ2FpbiwgdGhhdCB3b3VsZCByZXR1cm4gdGhlIHdob2xlIHF1ZXJ5IGF0IG9uY2UsIGluc3RlYWQgb2YgbGF6aWx5LCBzbyBpZiB5b3Ugd2FudCB0byBpdGVyYXRlIG92ZXIgZGlmZmVyZW50IHZlcnNpb25zIG9mIHlvdXIgcXVlcnkgZm9yIHRlc3RpbmcsIGl0IGNhbiBiZSBzbG93IGFuZCBlYXQgdXAgbWVtb3J5LiBBbGwgdGhlIGV4cGxpY2l0IG5hbWluZyBhbmQgcXVvdGluZyB0aGF0IGB7ZGJwbHlyfWAgaXMgZG9pbmcgd2hlbiBpdCB0cmFuc2xhdGVzIHRvIFNRTCBwcmV2ZW50cyBhIGxvdCBvZiBlcnJvcnMsIGJ1dCBpZiB5b3VyIGhhbmQtd3JpdHRlbiBTUUwgaXMgYmV0dGVyIHRoYW4gbWluZSwgeW91IGNhbiB1c2UgaXQgc3RyYWlnaHQgaW5zdGVhZC4KCkxldCdzIGxvb2sgYXQgcm93cyB0aGF0IGhhdmUgYWxyZWFkeSBoYWQgdGhlIGJhZ3MgcnVuIG9uIHRoZSBDOk4gYW5hbHl6ZXIgdG8gZ2V0ICVOIGNvbnRlbnQ6CgpgYGB7ciBwYWdlZC5wcmludCA9IEZBTFNFfQpuX2RhdGEgPC0gYmFnX2RhdGEgJT4lIAogIGZpbHRlcighaXMubmEocGVyY2VudF9uKSkgJT4lIAogIGNvbGxlY3QoKQoKbl9kYXRhCgpnZ3Bsb3Qobl9kYXRhLCBhZXModGltZSwgcGVyY2VudF9uLCBncm91cCA9IHRpbWUpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgojIE1vcmUgY29tcGxleCBxdWVyaWVzCgpMZXQncyB0cnkgYW4gZXhhbXBsZSB3aXRoIHRocmVlIHRhYmxlcy4gSSdsbCBzcGxpdCBpdCB1cCBpbnRvIHNldmVyYWwgc2VwYXJhdGUgcXVlcmllcyB0byBtYWtlIGl0IGEgbGl0dGxlIG1vcmUgY2xlYXIgd2hhdCBlYWNoIHN0ZXAgaXMgZG9pbmcsIGJ1dCB0aGV5IGNvdWxkIGFsbCBiZSBjaGFpbmVkIHRvZ2V0aGVyIGludG8gYSBzaW5nbGUgc3RhdGVtZW50LiAKCjEuIFdlJ2xsIHVzZSB0aGUgKipgZGVjb21wX2Jpb21hc3NgKiogdGFibGUgdG8gZXN0aW1hdGUgbW9pc3R1cmUgY29udGVudCBmb3IgYmFncyBhdCAqKmB0aW1lID09IDBgKiogKGZpcnN0IGNvbGxlY3Rpb24pLiAKMi4gVGhlbiB3ZSdsbCBqb2luIGl0IHRvIHRoZSAqKmBpbl9maWVsZF9iaW9tYXNzYCoqIHRhYmxlIGFuZCBjYWxjdWxhdGUgZHJ5IGVxdWl2YWxlbnRzIG9mIHRoZSBmcmVzaCBiaW9tYXNzIHRoZXJlLiAKMy4gVGhlbiB3ZSdsbCBqb2luIGl0IGJhY2sgdG8gdGhlICoqYHNpdGVfaW5mb3JtYXRpb25gKiogdGFibGUgdG8gZ2V0IHN0YXRlIGFuZCB5ZWFyIGluZm9ybWF0aW9uLgo0LiBUaGVuIHdlJ2xsIGNvbnZlcnQgdGhlIHBlcmNlbnQtcXVhbGl0eSBjb2x1bW5zIGludG8gdG90YWwgJGdyYW1zL21ldGVyXnsyfSQgdW5pdHMuCjUuIEZpbmFsbHkgd2UnbGwgcHVsbCB0aGUgcXVlcnkgaW4gbG9jYWxseSBhbmQgZG8gc29tZSB2aXN1YWxpemF0aW9uIGFuZCBhbmFseXNpcy4KCiMjIE1vaXN0dXJlIGNvbnRlbnQgZXN0aW1hdGVzCgpgYGB7ciBwYWdlZC5wcmludCA9IEZBTFNFfQoKYmFnX21vaXN0dXJlX3F1ZXJ5IDwtIHRibChjb24sICJkZWNvbXBfYmlvbWFzcyIpICU+JSAKICBmaWx0ZXIodGltZSA9PSAwKSAlPiUgCiAgbXV0YXRlKG1fcmF0aW8gPSBkcnlfYmlvbWFzc193dCAvIGZyZXNoX2Jpb21hc3Nfd3QpICU+JSAKICBncm91cF9ieShjb2RlLCBzdWJwbG90KSAlPiUgCiAgc3VtbWFyaXplKG1fcmF0aW8gPSBtZWFuKG1fcmF0aW8sIG5hLnJtID0gVFJVRSkpCgpiYWdfbW9pc3R1cmVfcXVlcnkKCmBgYAoKSGVyZSB3ZSBsb29rZWQgdXAgdGhlIGRlY29tcCB0YWJsZSwgZmlsdGVyZWQgdG8gb25seSB0aGUgdGltZS16ZXJvIGJhZ3MsIGFuZCBjYWxjdWxhdGVkIHRoZSByYXRpbyBvZiBkcnk6ZnJlc2ggYmlvbWFzcyB3ZWlnaHRzLiBUaGVuIHdlIHNlcGFyYXRlIGl0IGludG8gZ3JvdXBzIGZvciBlYWNoIGZhcm0gY29kZSBhbmQgc3VicGxvdCAoc2hvdWxkIGJlIHR3byByb3dzIHBlciBncm91cCwgc3Vic2FtcGxlcyBBIGFuZCBCKS4gRm9yIGVhY2ggZ3JvdXAsIHdlIHRoZW4gY2FsY3VsYXRlIHRoZSBtZWFuIG9mIHRoYXQgbW9pc3R1cmUgcmF0aW8uCgojIyBHZXQgZHJ5IG1hdHRlciBlcXVpdmFsZW50cyBmb3IgYnVsayBjb3ZlciBjcm9wIG1hdGVyaWFsCgpgYGB7ciBwYWdlZC5wcmludCA9IEZBTFNFfQoKaW5fZmllbGRfcXVlcnkgPC0gZnVsbF9qb2luKAogIGJhZ19tb2lzdHVyZV9xdWVyeSwKICB0YmwoY29uLCAiaW5fZmllbGRfYmlvbWFzcyIpCiAgKSAlPiUgCiAgbXV0YXRlKAogICAgZHJ5X3d0X2VzdF9nX20yID0gbV9yYXRpbyAqIChmcmVzaF93dF9hICsgZnJlc2hfd3RfYikgLyAyCiAgICApCgppbl9maWVsZF9xdWVyeQoKYGBgCgpUaGUgZmlyc3QgcXVlcnkgKCoqYGJhZ19tb2lzdHVyZV9xdWVyeWAqKikgY29uc3RydWN0ZWQgYSB0ZW1wb3JhcnkgdGFibGUgaW5zaWRlIHRoZSBkYXRhYmFzZS4gTm93IHdlJ3JlIHRlbGxpbmcgdGhlIERCIHdlIHdhbnQgdG8gZG8gYSBmdWxsIChvciBvdXRlcikgam9pbiBvbiB0aGF0IHRlbXBvcmFyeSB0YWJsZSBhbmQgdGhlICoqYGluX2ZpZWxkX2Jpb21hc3NgKiogdGFibGUgKGNvbnRhaW5pbmcgYWxsIG1hdGNoZXMgYmV0d2VlbiByb3dzLCBhbmQgZXZlcnkgY29sdW1uIGluIGJvdGggdGFibGVzKS4gU2luY2Ugd2UgY29uc3RydWN0ZWQgdGhlIHRlbXBvcmFyeSB0YWJsZSB0byBvbmx5IGhhdmUgdGhyZWUgY29sdW1ucyAoKipgY29kZWAqKiwgKipgc3VicGxvdGAqKiwgYW5kICoqYG1fcmF0aW9gKiopLCB3ZSBkb24ndCBoYXZlIHRvIHdvcnJ5IGFib3V0IHNwZWNpZnlpbmcgd2hpY2ggY29sdW1ucyB0byBqb2luIG9uOiB3ZSBzaG91bGQgZ2V0IHBlcmZlY3QgMToxIG1hdGNoaW5nIG9uICoqYGNvZGVgKiogYW5kICoqYHN1YnBsb3RgKiouIEEgaGVscGZ1bCBtZXNzYWdlIGlzIHByaW50ZWQgbGV0dGluZyB1cyBrbm93IHRob3NlIGFyZSB0aGUgY29tbW9uIGNvbHVtbnMuCgpBZnRlciB0aGUgam9pbiwgd2UgdGhlbiBtYWtlIGEgbmV3IGNvbHVtbiBpbiB0aGlzIG5ldyB0ZW1wb3JhcnkgdGFibGUuIFRoZSBuZXcgY29sdW1uIGlzIHRoZSBhdmVyYWdlIG9mIHRoZSB0d28gc3Vic2FtcGxlcyBvZiBmcmVzaCB3ZWlnaHRzLCB0aW1lcyB0aGUgbW9pc3R1cmUgcmF0aW8gd2UgY2FsY3VsYXRlZCBiZWZvcmUuIEFnYWluLCBzaW1wbGUgYXJpdGhtZXRpYyBvcGVyYXRpb25zIGxpa2UgdGhpcyBhcmUgZWFzeSBmb3IgdGhlIGRhdGFiYXNlIHRvIGRvIGludGVybmFsbHkuIE5vdyB3ZSBoYXZlIGEgdGVtcG9yYXJ5IHRhYmxlIHdpdGggYWxsIHRoZSBiaW9tYXNzIHByb3BlcnRpZXMsIGluY2x1ZGluZyBhbiBlc3RpbWF0ZSBvZiBkcnkgbWF0dGVyIGNvbnRlbnQgaW4gJGdyYW1zL21ldGVyXnsyfSQuCgojIyBDb21iaW5lIHdpdGggc2l0ZSBpbmZvCgpgYGB7ciBwYWdlZC5wcmludCA9IEZBTFNFfQpiaW9tYXNzX3dpdGhfc2l0ZV9xdWVyeSA8LSBmdWxsX2pvaW4oCiAgdGJsKGNvbiwgInNpdGVfaW5mb3JtYXRpb24iKSwKICBpbl9maWVsZF9xdWVyeSwKICBieSA9ICJjb2RlIgopICU+JSAKICBhcnJhbmdlKHllYXIsIHN0YXRlKQoKYmlvbWFzc193aXRoX3NpdGVfcXVlcnkKYGBgCgpOb3cgd2UgdGFrZSB0aGUgdGVtcG9yYXJ5IHRhYmxlIGFuZCBkbyBhbm90aGVyIGZ1bGwvb3V0ZXIgam9pbiwgdGhpcyB0aW1lIHdpdGggdGhlIHNpdGUgaW5mbyB0YWJsZS4gU2luY2Ugd2UgZGlkbid0IHN0cmlwIG91dCBhbnkgY29sdW1ucyBpbiB0aGUgbGFzdCBxdWVyeSwgaXQncyBhIGdvb2QgaWRlYSB0byBzcGVjaWZ5ICoqYGJ5ID0gImNvZGUiYCoqIHRvIG1ha2Ugc3VyZSBpdCBpZ25vcmVzIGFueSBvdGhlciBjb21tb24gY29sdW1ucyAoaW4gcGFydGljdWxhciB0aGUgaW50ZXJuYWwgcm93IGlkZW50aWZpZXIsICoqYGNpZGAqKikuIE5vdyB3ZSBoYXZlIGEgdGVtcG9yYXJ5IHRhYmxlIHdpdGggYWxsIHRoZSBiaW9tYXNzIHByb3BlcnRpZXMsIGFzIHdlbGwgYXMgaW5mb3JtYXRpb24gYWJvdXQgd2hpY2ggc3RhdGUgYW5kIHllYXIgdGhleSB3ZXJlIHJlY29yZGVkIGluLgoKIyMgQ29udmVydCB1bml0cwoKYGBge3IgcGFnZWQucHJpbnQgPSBGQUxTRX0KYmlvbWFzc193aXRoX3VuaXRzX3F1ZXJ5IDwtIGJpb21hc3Nfd2l0aF9zaXRlX3F1ZXJ5ICU+JSAKICBtdXRhdGVfYXQoCiAgICB2YXJzKG1hdGNoZXMoInBlcmNlbnRfIikpLCAKICAgIGxpc3QoImdfbTIiID0gfi4qZHJ5X3d0X2VzdF9nX20yLzEwMCkKICAgICkKCiMgSWYgeW91IHdhbnQgdG8gc2VlIGEgcmVhbCBtb3V0aGZ1bCwgc2hvdyB0aGUgdW5kZXJseWluZyBTUUwgY2FsbApzaG93X3F1ZXJ5KGJpb21hc3Nfd2l0aF91bml0c19xdWVyeSkKYGBgCgpUaGUgbGFzdCBzdGVwIGlzIHRvIGNyZWF0ZSBuZXcgY29sdW1ucyB1c2luZyBhbiAiYW5vbnltb3VzIGZ1bmN0aW9uIjogKipgfi4qZHJ5X3d0X2VzdF9nX20yLzEwMGAqKi4gVGhpcyBpcyB0aGUgc2FtZSB0aGluZyBhcyBhIGZ1bmN0aW9uIHRoYXQgbG9va3MgbGlrZSBgbXVsdGlwbGllciA8LSBmdW5jdGlvbihwY3QpIHsgcGN0ICogZHJ5X3d0X2VzdF9nX20yIC8gMTAwfWAuIFdlIHRoZW4gdXNlIHRoYXQgZnVuY3Rpb24gb24gYWxsIHRoZSB2YXJpYWJsZXMvY29sdW1ucyAoKipgdmFycygpYCoqKSB0aGF0IG1hdGNoIHRoZSB0ZXh0ICoqYCJwZXJjZW50XyJgKiouIEZvciBldmVyeSBzdWNoIGNvbHVtbiwgaXQgd2lsbCBub3cgbWFrZSBhIG5ldyBjb2x1bW4gd2l0aCB0aGUgbmFtZSAqKmBwZXJjZW50X2Zvb19nX20yYCoqLCB3aGljaCB3aWxsIGJlIHRoZSB0b3RhbCBhbW91bnQgb2YgImZvbyIgaW4gJGdyYW1zL21ldGVyXnsyfSQuIAoKWW91IGNhbiBhbHNvIHNlZSBJIHByaW50ZWQgb3V0IHRoZSBnZW5lcmF0ZWQgU1FMIHF1ZXJ5LCBzaW5jZSB0aGlzIGlzIHN0aWxsIGFsbCBoYXBwZW5pbmcgaW5zaWRlIHRoZSBkYXRhYmFzZS4gWW91IGNvdWxkIGhhdmUgcHVsbGVkIHRoZSBxdWVyeSBpbiBsb2NhbGx5IGF0IGEgbnVtYmVyIG9mIHBvaW50cyBkdXJpbmcgdGhpcyBjaGFpbiwgYW5kIHRoZSBge2RwbHlyfWAgc3ludGF4IHdvdWxkIGhhdmUgYmVlbiBpZGVudGljYWwgKHRoZSBjYWxscyB0byAqKmBmaWx0ZXJgLCBgbXV0YXRlYCwgYHN1bW1hcml6ZWAsIGBncm91cF9ieWAsIGBmdWxsX2pvaW5gKiopOyBqdXN0IHRoZSBjYWxscyB0byAqKmB0YmwoY29uLCAidGFibGVfbmFtZSIpYCoqIHdvdWxkIGhhdmUgYmVlbiB5b3VyIGxvY2FsIHZhcmlhYmxlcyBpbnN0ZWFkLgoKQWdhaW4sIHRoaXMgaXMgb3Zlcmx5LWV4cGxpY2l0IGdlbmVyYXRlZCBTUUwsIHdoaWNoIGNvdWxkIHByb2JhYmx5IGJlIHNpbXBsaWZpZWQgaWYgeW91IHdyb3RlIGl0IGJ5IGhhbmQuIEhvd2V2ZXIsIHRoZSBge2RicGx5cn1gLXRyYW5zbGF0ZWQgdmVyc2lvbiBjaGVja3MgZm9yIGEgbG90IG9mIHBvc3NpYmxlIGVycm9ycyBhbmQgdHJpZXMgdG8gb3B0aW1pemUgdGhlIG51bWJlciBvZiBvcGVyYXRpb25zIGZvciBzcGVlZCBhbmQgbWVtb3J5LWVmZmljaWVuY3kuCgojIyBDb21wbGV0ZSB0aGUgcXVlcnkKCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9Cgpsb2NhbF9iaW9tYXNzX2RmIDwtIGNvbGxlY3QoYmlvbWFzc193aXRoX3VuaXRzX3F1ZXJ5KSAlPiUgCiAgZmlsdGVyKCFpcy5uYShzdGF0ZSkpCgpsb2NhbF9iaW9tYXNzX2RmCmBgYAoKTm93IGFsbCB3ZSBoYWQgbGVmdCB3YXMgdG8gcHVsbCB0aGF0IGxvbmcgMy10YWJsZSBxdWVyeSBpbnRvIHlvdXIgbG9jYWwgUiBlbnZpcm9ubWVudCBhbmQgZG8gc29tZSB2aXN1YWxpemF0aW9uIGFuZCBhbmFseXNpcy4gCgojIyBBbmFseXppbmcgdGhlIHJlc3VsdHMKCkxldCdzIGxvb2sgYXQgaGVtaWNlbGx1bG9zZSBjb250ZW50IGFzIGFuIGV4YW1wbGUuCgpgYGB7ciBwYWdlZC5wcmludCA9IEZBTFNFfQpsb2NhbF9iaW9tYXNzX2RmICU+JSAKICBmaWx0ZXIoIWlzLm5hKHBlcmNlbnRfaGVtaWNlbGxfY2FsY19nX20yKSkgJT4lIAogIGdncGxvdChhZXMoc3RhdGUsIHBlcmNlbnRfaGVtaWNlbGxfY2FsY19nX20yKSkgKwogIGdlb21fYm94cGxvdCgpICsgCiAgZmFjZXRfZ3JpZCgKICAgIH55ZWFyLCAKICAgIHNwYWNlID0gImZyZWVfeCIsIAogICAgc2NhbGVzID0gImZyZWVfeCIKICAgICkKYGBgCgpXaGF0IGFib3V0IHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gdGhlIGNvbXBvc2l0aW9uIGRhdGE/IExldCdzIGxvb2sgYXQgY3J1ZGUgcHJvdGVpbiBjb250ZW50IGFzIGEgZnVuY3Rpb24gb2YgdG90YWwgZHJ5IG1hdHRlcjoKCmBgYHtyIHBhZ2VkLnByaW50ID0gRkFMU0V9CmxvY2FsX2Jpb21hc3NfZGYgJT4lIAogIGZpbHRlcighaXMubmEocGVyY2VudF9jcCksICFpcy5uYShkcnlfd3RfZXN0X2dfbTIpKSAlPiUKICBnZ3Bsb3QoYWVzKGRyeV93dF9lc3RfZ19tMiwgcGVyY2VudF9jcCwgY29sb3IgPSBmYWN0b3IoeWVhcikpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV94X2xvZzEwKCkgKyAKICBzY2FsZV95X2xvZzEwKCkgKyAKICBzdGF0X3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKQpgYGAKCgoKIyBBbmQgZG9uJ3QgZm9yZ2V0IHRvIGNsZWFuIHVwIGFmdGVyCgpgYGB7cn0KZGJEaXNjb25uZWN0KGNvbikKYGBgCg==

precisionsustainableag.org


Icons: fontawesome.com, BY 4.0 | This work, BY-SA 4.0