Introduction

GROUP BY and PARTITION BY represent two fundamentally different operations in SQL. GROUP BY performs aggregation by collapsing rows into groups, returning one row per group. PARTITION BY, used with window functions, divides rows into partitions but preserves all original rows while adding computed values based on partition-level calculations. Understanding this distinction is essential for writing efficient analytical queries.

Execution Order and Query Processing

SQL queries execute in a specific order that determines how GROUP BY and PARTITION BY interact. The execution sequence proceeds as follows: FROM and JOIN clauses retrieve data, WHERE filters rows, GROUP BY aggregates and collapses rows, HAVING filters groups, SELECT applies window functions including PARTITION BY, ORDER BY sorts results, and finally LIMIT restricts output. This sequence means GROUP BY executes before window functions, so PARTITION BY operates on already-aggregated data when both appear in the same query.

Sample Dataset

To illustrate the use of GROUP BY and PARTITION BY, consider an employee salary dataset:

CREATE TABLE employees (
    employee_id INTEGER,
    name TEXT,
    department TEXT,
    salary INTEGER,
    hire_date DATE
);
INSERT INTO employees VALUES
(1, 'Kern, Jason', 'Sales', 60000, '2020-01-15'),
(2, 'Samansky, Alison', 'Sales', 65000, '2019-03-20'),
(3, 'McCullum, Charlie', 'Sales', 70000, '2018-07-10'),
(4, 'Kaiser, David', 'Engineering', 80000, '2021-02-01'),
(5, 'da Silva, Evina', 'Engineering', 85000, '2020-06-15'),
(6, 'Olase, Francis', 'Engineering', 90000, '2019-11-30'),
(7, 'Kirti, Gautham', 'HR', 55000, '2021-08-22'),
(8, 'Cheng, Yufan', 'HR', 58000, '2020-12-05');

GROUP BY Behavior

GROUP BY collapses rows based on specified grouping columns and requires aggregate functions for non-grouped columns. The operation fundamentally reduces the result set size.

SELECT 
    department,
    COUNT(*) as employee_count,
    AVG(salary) as avg_salary,
    MIN(salary) as min_salary,
    MAX(salary) as max_salary
FROM employees
GROUP BY department;
Table 1: 3 records
department employee_count avg_salary min_salary max_salary
Engineering 3 85000 80000 90000
HR 2 56500 55000 58000
Sales 3 65000 60000 70000

This query returns three rows, one per department, with aggregated statistics. The original eight employee records collapse into three department summaries. Any column in the SELECT clause must either appear in the GROUP BY clause or be wrapped in an aggregate function.

-- Invalid: name is neither grouped nor aggregated
SELECT department, name, AVG(salary) 
  FROM employees 
 GROUP BY department;

-- Valid: all non-aggregated columns are in GROUP BY
SELECT department, name, AVG(salary) as avg_salary
  FROM employees
 GROUP BY department, name;

PARTITION BY Behavior

PARTITION BY divides rows into partitions for window function calculations while preserving all original rows. Window functions compute values based on sets of rows related to the current row. Notice how the PARTITION BY clause is part of the SELECT clause and applied to aggregation functions. Of course, some aggregation results will be the same for all rows, but others can be the result of calculations over partitions.

SELECT 
    employee_id,
    name,
    department,
    salary,
    COUNT(*) OVER (PARTITION BY department) as dept_employee_count,
    AVG(salary) OVER (PARTITION BY department) as dept_avg_salary,
    salary - AVG(salary) OVER (PARTITION BY department) as diff_from_avg
FROM employees;
Table 2: 8 records
employee_id name department salary dept_employee_count dept_avg_salary diff_from_avg
4 Kaiser, David Engineering 80000 3 85000 -5000
5 da Silva, Evina Engineering 85000 3 85000 0
6 Olase, Francis Engineering 90000 3 85000 5000
7 Kirti, Gautham HR 55000 2 56500 -1500
8 Cheng, Yufan HR 58000 2 56500 1500
1 Kern, Jason Sales 60000 3 65000 -5000
2 Samansky, Alison Sales 65000 3 65000 0
3 McCullum, Charlie Sales 70000 3 65000 5000

This query returns all eight rows with department-level statistics added to each row. Every employee in Sales has the same department average of 65000, while every employee in Engineering has 85000. The partition-level calculations are repeated for each row in the partition.

Ranking and Analytical Functions

Window functions support operations impossible with GROUP BY alone. Ranking functions assign sequential numbers to rows within partitions.

SELECT 
    name,
    department,
    salary,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as salary_rank,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank_with_ties,
    DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dense_rank
FROM employees;
Table 3: 8 records
name department salary salary_rank rank_with_ties dense_rank
Olase, Francis Engineering 90000 1 1 1
da Silva, Evina Engineering 85000 2 2 2
Kaiser, David Engineering 80000 3 3 3
Cheng, Yufan HR 58000 1 1 1
Kirti, Gautham HR 55000 2 2 2
McCullum, Charlie Sales 70000 1 1 1
Samansky, Alison Sales 65000 2 2 2
Kern, Jason Sales 60000 3 3 3

ROW_NUMBER assigns unique sequential integers even when values tie. RANK assigns the same rank to tied values and skips subsequent ranks. DENSE_RANK assigns the same rank to ties without skipping ranks. Within the Sales department, Charlie ranks first at 70000, Bob second at 65000, and Alice third at 60000.

LEAD and LAG functions access values from other rows relative to the current row within a partition.

SELECT 
    name,
    department,
    salary,
    LAG(salary) OVER (PARTITION BY department ORDER BY salary) as prev_salary,
    LEAD(salary) OVER (PARTITION BY department ORDER BY salary) as next_salary,
    salary - LAG(salary) OVER (PARTITION BY department ORDER BY salary) as increase
FROM employees
ORDER BY department, salary;
Table 4: 8 records
name department salary prev_salary next_salary increase
Kaiser, David Engineering 80000 NA 85000 NA
da Silva, Evina Engineering 85000 80000 90000 5000
Olase, Francis Engineering 90000 85000 NA 5000
Kirti, Gautham HR 55000 NA 58000 NA
Cheng, Yufan HR 58000 55000 NA 3000
Kern, Jason Sales 60000 NA 65000 NA
Samansky, Alison Sales 65000 60000 70000 5000
McCullum, Charlie Sales 70000 65000 NA 5000

Combining GROUP BY and PARTITION BY

Queries can combine both operations by using subqueries or common table expressions. GROUP BY first aggregates data, then PARTITION BY performs window calculations on the aggregated results. We will illustrate this with a different database.

-- Create the table first
CREATE TABLE test_scores (
    student_id INTEGER,
    student_name TEXT,
    class TEXT,
    test_name TEXT,
    score INTEGER
);
-- Insert sample data
INSERT INTO test_scores VALUES
(1, 'Alice', 'Math', 'Test1', 85),
(1, 'Alice', 'Math', 'Test2', 90),
(1, 'Alice', 'Math', 'Test3', 88),
(2, 'Bob', 'Math', 'Test1', 78),
(2, 'Bob', 'Math', 'Test2', 82),
(2, 'Bob', 'Math', 'Test3', 80),
(3, 'Charlie', 'Science', 'Test1', 92),
(3, 'Charlie', 'Science', 'Test2', 95),
(3, 'Charlie', 'Science', 'Test3', 94),
(4, 'David', 'Science', 'Test1', 88),
(4, 'David', 'Science', 'Test2', 90),
(4, 'David', 'Science', 'Test3', 89);
SELECT 
    student_name,
    class,
    avg_score,
    RANK() OVER (PARTITION BY class ORDER BY avg_score DESC) as class_rank,
    AVG(avg_score) OVER (PARTITION BY class) as class_overall_avg
FROM (
    SELECT 
        student_name,
        class,
        AVG(score) as avg_score
    FROM test_scores
    GROUP BY student_name, class
)
ORDER BY class, class_rank;
Table 5: 4 records
student_name class avg_score class_rank class_overall_avg
Alice Math 87.66667 1 83.83333
Bob Math 80.00000 2 83.83333
Charlie Science 93.66667 1 91.33333
David Science 89.00000 2 91.33333
-- Calculate student averages, then rank within classes
SELECT 
    student_name,
    class,
    avg_score,
    RANK() OVER (PARTITION BY class ORDER BY avg_score DESC) as class_rank,
    AVG(avg_score) OVER (PARTITION BY class) as class_overall_avg
FROM (
    SELECT 
        student_name,
        class,
        AVG(score) as avg_score
    FROM test_scores
    GROUP BY student_name, class
) student_averages
ORDER BY class, class_rank;
Table 6: 4 records
student_name class avg_score class_rank class_overall_avg
Alice Math 87.66667 1 83.83333
Bob Math 80.00000 2 83.83333
Charlie Science 93.66667 1 91.33333
David Science 89.00000 2 91.33333

The subquery first collapses multiple test scores per student into a single average using GROUP BY. The outer query then applies window functions to rank students within their classes and compare individual averages to class averages. This pattern is common in analytical queries requiring both aggregation and comparative analysis.

Sales Analysis Example

Consider a sales dataset demonstrating practical applications of combined GROUP BY and PARTITION BY operations.

CREATE TABLE sales (
    sale_id INTEGER,
    salesperson TEXT,
    region TEXT,
    sale_amount REAL,
    sale_date TEXT
);
INSERT INTO sales VALUES
(1, 'Alice', 'North', 1000, '2024-01-15'),
(2, 'Alice', 'North', 1500, '2024-01-20'),
(3, 'Bob', 'North', 800, '2024-01-18'),
(4, 'Bob', 'North', 1200, '2024-01-22'),
(5, 'Charlie', 'South', 2000, '2024-01-16'),
(6, 'Charlie', 'South', 1800, '2024-01-25'),
(7, 'David', 'South', 1500, '2024-01-17'),
(8, 'David', 'South', 1600, '2024-01-23');

A comprehensive analysis ranks salespeople within regions while calculating regional statistics.

SELECT 
    salesperson,
    region,
    total_sales,
    RANK() OVER (PARTITION BY region ORDER BY total_sales DESC) as regional_rank,
    AVG(total_sales) OVER (PARTITION BY region) as regional_avg,
    total_sales - AVG(total_sales) OVER (PARTITION BY region) as diff_from_avg,
    ROUND(100.0 * total_sales / SUM(total_sales) OVER (PARTITION BY region), 2) as pct_of_regional_total
FROM (
    SELECT 
        salesperson,
        region,
        SUM(sale_amount) as total_sales
    FROM sales
    GROUP BY salesperson, region
) salesperson_totals
ORDER BY region, regional_rank;
Table 7: 4 records
salesperson region total_sales regional_rank regional_avg diff_from_avg pct_of_regional_total
Alice North 2500 1 2250 250 55.56
Bob North 2000 2 2250 -250 44.44
Charlie South 3800 1 3450 350 55.07
David South 3100 2 3450 -350 44.93

The inner query aggregates individual sales into per-salesperson totals using GROUP BY. The outer query then ranks salespeople within regions and calculates each salesperson’s percentage of regional total sales. Alice leads the North region with 2500 in total sales, representing 55.56 percent of the regional total, while Bob trails with 2000 representing 44.44 percent.

Running Totals and Window Frames

Window functions support frame specifications for calculating running aggregates. The frame defines which rows relative to the current row participate in the calculation.

SELECT 
    name,
    department,
    salary,
    hire_date,
    SUM(salary) OVER (
        PARTITION BY department 
        ORDER BY hire_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) as cumulative_dept_salary
FROM employees
ORDER BY department, hire_date;
Table 8: 8 records
name department salary hire_date cumulative_dept_salary
Olase, Francis Engineering 90000 2019-11-30 90000
da Silva, Evina Engineering 85000 2020-06-15 175000
Kaiser, David Engineering 80000 2021-02-01 255000
Cheng, Yufan HR 58000 2020-12-05 58000
Kirti, Gautham HR 55000 2021-08-22 113000
McCullum, Charlie Sales 70000 2018-07-10 70000
Samansky, Alison Sales 65000 2019-03-20 135000
Kern, Jason Sales 60000 2020-01-15 195000

This query calculates a running total of salaries within each department ordered by hire date. For each employee, the running total includes all employees hired in the same department up to and including the current employee’s hire date. The frame clause “ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW” specifies this behavior explicitly.

Decision Framework

Choose GROUP BY when creating summary reports that require one row per group, when reducing result sets through aggregation, or when building dashboards with aggregate statistics. The operation is appropriate when detail-level data is unnecessary in the output.

Choose PARTITION BY when preserving detail rows while adding aggregate context, when ranking rows within groups, when comparing individual values to group statistics, or when calculating running totals and moving averages. Window functions excel at analytical queries requiring both detail and summary information simultaneously.

Combine both operations when first aggregating data then performing comparative analysis on the aggregates, when ranking aggregated groups, or when calculating percentages of group totals from already-aggregated data.

Common Errors

Attempting to select non-aggregated columns without including them in GROUP BY causes errors. Every column in the SELECT clause must either appear in GROUP BY or be wrapped in an aggregate function.

-- Error: name must be grouped or aggregated
SELECT department, name, AVG(salary) 
  FROM employees 
 GROUP BY department;

-- Correct: include name in GROUP BY
SELECT department, name, AVG(salary) 
  FROM employees 
 GROUP BY department, name;

When combining GROUP BY and PARTITION BY, ensure partition columns exist in the grouped result set. If GROUP BY collapses away a column, that column cannot be used in PARTITION BY.

-- Error: employee_id grouped away but needed for partition
SELECT department, SUM(salary),
       RANK() OVER (PARTITION BY employee_id ORDER BY salary)
  FROM employees GROUP BY department;

-- Correct: partition by what remains after grouping
SELECT department, SUM(salary) as total_salary,
       RANK() OVER (ORDER BY SUM(salary) DESC) as dept_rank
  FROM employees GROUP BY department;

Expecting PARTITION BY to reduce row counts is a common misconception. Window functions always preserve the input row count unless DISTINCT or additional filtering is applied.

Performance Considerations

Window functions typically perform better than equivalent self-joins or correlated subqueries. Modern query optimizers recognize window function patterns and generate efficient execution plans. However, partitioning on unindexed columns or ordering by expressions can degrade performance on large datasets.

Consider creating indexes on columns used in PARTITION BY and ORDER BY clauses for large tables. When combining GROUP BY and PARTITION BY, the initial aggregation reduces the dataset size before window calculations, often improving overall query performance compared to window functions on raw data.

Multiple window functions with identical PARTITION BY and ORDER BY clauses can share computation. Modern optimizers recognize this pattern and avoid redundant sorting and partitioning operations.

-- Efficient: same window specification reused
SELECT 
    name,
    salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank,
    DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dense_rank,
    ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as row_num
FROM employees;

Understanding the distinction between GROUP BY aggregation and PARTITION BY window functions, their execution order, and their appropriate use cases enables construction of sophisticated analytical queries that efficiently answer complex business questions.


Files & Resources

All Files for Lesson 70.115

Notes

SQLite added comprehensive window function support including PARTITION BY in version 3.25.0 released in September 2018. Any modern SQLite installation supports these features. Check the version with SELECT sqlite_version(); to confirm compatibility. Versions prior to 3.25.0 require complex workarounds using self-joins and subqueries that are both inefficient and difficult to maintain.

Acknowledgements

Claude 4.6 (Sonnet) was used in the preparation of this lesson.

Errata

Let us know.

LS0tCnRpdGxlOiAiR1JPVVAgQlkgYW5kIFBBUlRJVElPTiBCWTogVHdvIEFwcHJvYWNoZXMgdG8gQW5hbHl0aWNhbCBRdWVyaWVzIGluIFNRTCIKcGFyYW1zOgogIGNhdGVnb3J5OiA3MAogIG51bWJlcjogMTE1CiAgdGltZTogNDUKICBsZXZlbDogYmVnaW5uZXIKICB0YWdzOiAic3FsLGdyb3VwIGJ5LHBhcnRpdGlvbixhbmFseXRpY3MiCiAgZGVzY3JpcHRpb246ICJFeGFtaW5lcyB0aGUgZnVuZGFtZW50YWwgZGlmZmVyZW5jZXMgYmV0d2VlbiBHUk9VUCBCWSAKICAgICAgICAgICAgICAgIGFnZ3JlZ2F0aW9uLCB3aGljaCBjb2xsYXBzZXMgcm93cyBpbnRvIHN1bW1hcnkgZ3JvdXBzLCAKICAgICAgICAgICAgICAgIGFuZCBQQVJUSVRJT04gQlkgd2luZG93IGZ1bmN0aW9ucywgd2hpY2ggcHJlc2VydmUgYWxsIHJvd3MgCiAgICAgICAgICAgICAgICB3aGlsZSBhZGRpbmcgcGFydGl0aW9uLWxldmVsIGNhbGN1bGF0aW9ucy4gQ29tYmluZSBib3RoIAogICAgICAgICAgICAgICAgb3BlcmF0aW9ucyBmb3IgY29tcGxleCBhbmFseXRpY2FsIHF1ZXJpZXMuIFByb3ZpZGVzCiAgICAgICAgICAgICAgICBleGFtcGxlcyB1c2luZyBTUUxpdGUgYW5kIFIgYnV0IGxlc3NvbiBpcyBhcHBsaWNhYmxlIHRvCiAgICAgICAgICAgICAgICBhbnkgZGF0YWJhc2UgYW5kIHByb2dyYW1taW5nIGxhbmd1YWdlLiIKZGF0ZTogIjxzbWFsbD5gciBTeXMuRGF0ZSgpYDwvc21hbGw+IgphdXRob3I6ICI8c21hbGw+TWFydGluIFNjaGVkbGJhdWVyPC9zbWFsbD4iCmVtYWlsOiAibS5zY2hlZGxiYXVlckBuZXUuZWR1IgphZmZpbGl0YXRpb246ICJOb3J0aGVhc3Rlcm4gVW5pdmVyc2l0eSIKb3V0cHV0OiAKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29sbGFwc2VkOiBmYWxzZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IGx1bWVuCiAgICBoaWdobGlnaHQ6IHRhbmdvCi0tLQoKLS0tCnRpdGxlOiAiPHNtYWxsPmByIHBhcmFtcyRjYXRlZ29yeWAuYHIgcGFyYW1zJG51bWJlcmA8L3NtYWxsPjxici8+PHNwYW4gc3R5bGU9J2NvbG9yOiAjMkU0MDUzOyBmb250LXNpemU6IDAuOWVtJz5gciBybWFya2Rvd246Om1ldGFkYXRhJHRpdGxlYDwvc3Bhbj4iCi0tLQoKYGBge3IgZWNobz1GfQojIFBhY2thZ2UgbmFtZXMKcGFja2FnZXMgPC0gYygiaGVyZSIsICJSU1FMaXRlIikKCiMgSW5zdGFsbCBwYWNrYWdlcyBub3QgeWV0IGluc3RhbGxlZAppbnN0YWxsZWRfcGFja2FnZXMgPC0gcGFja2FnZXMgJWluJSByb3duYW1lcyhpbnN0YWxsZWQucGFja2FnZXMoKSkKaWYgKGFueShpbnN0YWxsZWRfcGFja2FnZXMgPT0gRkFMU0UpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcyhwYWNrYWdlc1shaW5zdGFsbGVkX3BhY2thZ2VzXSwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnIikKfQoKIyBQYWNrYWdlcyBsb2FkaW5nCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhpbnZpc2libGUobGFwcGx5KHBhY2thZ2VzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKSkKYGBgCgpgYGB7ciBjb2RlPXhmdW46OnJlYWRfdXRmOChwYXN0ZTAoaGVyZTo6aGVyZSgpLCcvUi9faW5zZXJ0MkRCLlInKSksIGluY2x1ZGUgPSBGQUxTRX0KYGBgCgojIyBJbnRyb2R1Y3Rpb24KCkdST1VQIEJZIGFuZCBQQVJUSVRJT04gQlkgcmVwcmVzZW50IHR3byBmdW5kYW1lbnRhbGx5IGRpZmZlcmVudCBvcGVyYXRpb25zIGluIFNRTC4gR1JPVVAgQlkgcGVyZm9ybXMgYWdncmVnYXRpb24gYnkgY29sbGFwc2luZyByb3dzIGludG8gZ3JvdXBzLCByZXR1cm5pbmcgb25lIHJvdyBwZXIgZ3JvdXAuIFBBUlRJVElPTiBCWSwgdXNlZCB3aXRoIHdpbmRvdyBmdW5jdGlvbnMsIGRpdmlkZXMgcm93cyBpbnRvIHBhcnRpdGlvbnMgYnV0IHByZXNlcnZlcyBhbGwgb3JpZ2luYWwgcm93cyB3aGlsZSBhZGRpbmcgY29tcHV0ZWQgdmFsdWVzIGJhc2VkIG9uIHBhcnRpdGlvbi1sZXZlbCBjYWxjdWxhdGlvbnMuIFVuZGVyc3RhbmRpbmcgdGhpcyBkaXN0aW5jdGlvbiBpcyBlc3NlbnRpYWwgZm9yIHdyaXRpbmcgZWZmaWNpZW50IGFuYWx5dGljYWwgcXVlcmllcy4KCiMjIEV4ZWN1dGlvbiBPcmRlciBhbmQgUXVlcnkgUHJvY2Vzc2luZwoKU1FMIHF1ZXJpZXMgZXhlY3V0ZSBpbiBhIHNwZWNpZmljIG9yZGVyIHRoYXQgZGV0ZXJtaW5lcyBob3cgR1JPVVAgQlkgYW5kIFBBUlRJVElPTiBCWSBpbnRlcmFjdC4gVGhlIGV4ZWN1dGlvbiBzZXF1ZW5jZSBwcm9jZWVkcyBhcyBmb2xsb3dzOiBGUk9NIGFuZCBKT0lOIGNsYXVzZXMgcmV0cmlldmUgZGF0YSwgV0hFUkUgZmlsdGVycyByb3dzLCBHUk9VUCBCWSBhZ2dyZWdhdGVzIGFuZCBjb2xsYXBzZXMgcm93cywgSEFWSU5HIGZpbHRlcnMgZ3JvdXBzLCBTRUxFQ1QgYXBwbGllcyB3aW5kb3cgZnVuY3Rpb25zIGluY2x1ZGluZyBQQVJUSVRJT04gQlksIE9SREVSIEJZIHNvcnRzIHJlc3VsdHMsIGFuZCBmaW5hbGx5IExJTUlUIHJlc3RyaWN0cyBvdXRwdXQuIFRoaXMgc2VxdWVuY2UgbWVhbnMgR1JPVVAgQlkgZXhlY3V0ZXMgYmVmb3JlIHdpbmRvdyBmdW5jdGlvbnMsIHNvIFBBUlRJVElPTiBCWSBvcGVyYXRlcyBvbiBhbHJlYWR5LWFnZ3JlZ2F0ZWQgZGF0YSB3aGVuIGJvdGggYXBwZWFyIGluIHRoZSBzYW1lIHF1ZXJ5LgoKIyMgU2FtcGxlIERhdGFzZXQKClRvIGlsbHVzdHJhdGUgdGhlIHVzZSBvZiBHUk9VUCBCWSBhbmQgUEFSVElUSU9OIEJZLCBjb25zaWRlciBhbiBlbXBsb3llZSBzYWxhcnkgZGF0YXNldDoKCmBgYHtyIGVjaG89Riwgd2FybmluZz1GQUxTRX0KbGlicmFyeShSU1FMaXRlKQpkYmNvbiA8LSBkYkNvbm5lY3QoUlNRTGl0ZTo6U1FMaXRlKCksICI6bWVtb3J5OiIpCmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQpDUkVBVEUgVEFCTEUgZW1wbG95ZWVzICgKICAgIGVtcGxveWVlX2lkIElOVEVHRVIsCiAgICBuYW1lIFRFWFQsCiAgICBkZXBhcnRtZW50IFRFWFQsCiAgICBzYWxhcnkgSU5URUdFUiwKICAgIGhpcmVfZGF0ZSBEQVRFCik7CmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQpJTlNFUlQgSU5UTyBlbXBsb3llZXMgVkFMVUVTCigxLCAnS2VybiwgSmFzb24nLCAnU2FsZXMnLCA2MDAwMCwgJzIwMjAtMDEtMTUnKSwKKDIsICdTYW1hbnNreSwgQWxpc29uJywgJ1NhbGVzJywgNjUwMDAsICcyMDE5LTAzLTIwJyksCigzLCAnTWNDdWxsdW0sIENoYXJsaWUnLCAnU2FsZXMnLCA3MDAwMCwgJzIwMTgtMDctMTAnKSwKKDQsICdLYWlzZXIsIERhdmlkJywgJ0VuZ2luZWVyaW5nJywgODAwMDAsICcyMDIxLTAyLTAxJyksCig1LCAnZGEgU2lsdmEsIEV2aW5hJywgJ0VuZ2luZWVyaW5nJywgODUwMDAsICcyMDIwLTA2LTE1JyksCig2LCAnT2xhc2UsIEZyYW5jaXMnLCAnRW5naW5lZXJpbmcnLCA5MDAwMCwgJzIwMTktMTEtMzAnKSwKKDcsICdLaXJ0aSwgR2F1dGhhbScsICdIUicsIDU1MDAwLCAnMjAyMS0wOC0yMicpLAooOCwgJ0NoZW5nLCBZdWZhbicsICdIUicsIDU4MDAwLCAnMjAyMC0xMi0wNScpOwpgYGAKCiMjIEdST1VQIEJZIEJlaGF2aW9yCgpHUk9VUCBCWSBjb2xsYXBzZXMgcm93cyBiYXNlZCBvbiBzcGVjaWZpZWQgZ3JvdXBpbmcgY29sdW1ucyBhbmQgcmVxdWlyZXMgYWdncmVnYXRlIGZ1bmN0aW9ucyBmb3Igbm9uLWdyb3VwZWQgY29sdW1ucy4gVGhlIG9wZXJhdGlvbiBmdW5kYW1lbnRhbGx5IHJlZHVjZXMgdGhlIHJlc3VsdCBzZXQgc2l6ZS4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUIAogICAgZGVwYXJ0bWVudCwKICAgIENPVU5UKCopIGFzIGVtcGxveWVlX2NvdW50LAogICAgQVZHKHNhbGFyeSkgYXMgYXZnX3NhbGFyeSwKICAgIE1JTihzYWxhcnkpIGFzIG1pbl9zYWxhcnksCiAgICBNQVgoc2FsYXJ5KSBhcyBtYXhfc2FsYXJ5CkZST00gZW1wbG95ZWVzCkdST1VQIEJZIGRlcGFydG1lbnQ7CmBgYAoKVGhpcyBxdWVyeSByZXR1cm5zIHRocmVlIHJvd3MsIG9uZSBwZXIgZGVwYXJ0bWVudCwgd2l0aCBhZ2dyZWdhdGVkIHN0YXRpc3RpY3MuIFRoZSBvcmlnaW5hbCBlaWdodCBlbXBsb3llZSByZWNvcmRzIGNvbGxhcHNlIGludG8gdGhyZWUgZGVwYXJ0bWVudCBzdW1tYXJpZXMuIEFueSBjb2x1bW4gaW4gdGhlIFNFTEVDVCBjbGF1c2UgbXVzdCBlaXRoZXIgYXBwZWFyIGluIHRoZSBHUk9VUCBCWSBjbGF1c2Ugb3IgYmUgd3JhcHBlZCBpbiBhbiBhZ2dyZWdhdGUgZnVuY3Rpb24uCgpgYGAgc3FsCi0tIEludmFsaWQ6IG5hbWUgaXMgbmVpdGhlciBncm91cGVkIG5vciBhZ2dyZWdhdGVkClNFTEVDVCBkZXBhcnRtZW50LCBuYW1lLCBBVkcoc2FsYXJ5KSAKICBGUk9NIGVtcGxveWVlcyAKIEdST1VQIEJZIGRlcGFydG1lbnQ7CgotLSBWYWxpZDogYWxsIG5vbi1hZ2dyZWdhdGVkIGNvbHVtbnMgYXJlIGluIEdST1VQIEJZClNFTEVDVCBkZXBhcnRtZW50LCBuYW1lLCBBVkcoc2FsYXJ5KSBhcyBhdmdfc2FsYXJ5CiAgRlJPTSBlbXBsb3llZXMKIEdST1VQIEJZIGRlcGFydG1lbnQsIG5hbWU7CmBgYAoKIyMgUEFSVElUSU9OIEJZIEJlaGF2aW9yCgpQQVJUSVRJT04gQlkgZGl2aWRlcyByb3dzIGludG8gcGFydGl0aW9ucyBmb3Igd2luZG93IGZ1bmN0aW9uIGNhbGN1bGF0aW9ucyB3aGlsZSBwcmVzZXJ2aW5nIGFsbCBvcmlnaW5hbCByb3dzLiBXaW5kb3cgZnVuY3Rpb25zIGNvbXB1dGUgdmFsdWVzIGJhc2VkIG9uIHNldHMgb2Ygcm93cyByZWxhdGVkIHRvIHRoZSBjdXJyZW50IHJvdy4gTm90aWNlIGhvdyB0aGUgUEFSVElUSU9OIEJZIGNsYXVzZSBpcyBwYXJ0IG9mIHRoZSBTRUxFQ1QgY2xhdXNlIGFuZCBhcHBsaWVkIHRvIGFnZ3JlZ2F0aW9uIGZ1bmN0aW9ucy4gT2YgY291cnNlLCBzb21lIGFnZ3JlZ2F0aW9uIHJlc3VsdHMgd2lsbCBiZSB0aGUgc2FtZSBmb3IgYWxsIHJvd3MsIGJ1dCBvdGhlcnMgY2FuIGJlIHRoZSByZXN1bHQgb2YgY2FsY3VsYXRpb25zIG92ZXIgcGFydGl0aW9ucy4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUIAogICAgZW1wbG95ZWVfaWQsCiAgICBuYW1lLAogICAgZGVwYXJ0bWVudCwKICAgIHNhbGFyeSwKICAgIENPVU5UKCopIE9WRVIgKFBBUlRJVElPTiBCWSBkZXBhcnRtZW50KSBhcyBkZXB0X2VtcGxveWVlX2NvdW50LAogICAgQVZHKHNhbGFyeSkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQpIGFzIGRlcHRfYXZnX3NhbGFyeSwKICAgIHNhbGFyeSAtIEFWRyhzYWxhcnkpIE9WRVIgKFBBUlRJVElPTiBCWSBkZXBhcnRtZW50KSBhcyBkaWZmX2Zyb21fYXZnCkZST00gZW1wbG95ZWVzOwpgYGAKClRoaXMgcXVlcnkgcmV0dXJucyBhbGwgZWlnaHQgcm93cyB3aXRoIGRlcGFydG1lbnQtbGV2ZWwgc3RhdGlzdGljcyBhZGRlZCB0byBlYWNoIHJvdy4gRXZlcnkgZW1wbG95ZWUgaW4gU2FsZXMgaGFzIHRoZSBzYW1lIGRlcGFydG1lbnQgYXZlcmFnZSBvZiA2NTAwMCwgd2hpbGUgZXZlcnkgZW1wbG95ZWUgaW4gRW5naW5lZXJpbmcgaGFzIDg1MDAwLiBUaGUgcGFydGl0aW9uLWxldmVsIGNhbGN1bGF0aW9ucyBhcmUgcmVwZWF0ZWQgZm9yIGVhY2ggcm93IGluIHRoZSBwYXJ0aXRpb24uCgojIyBSYW5raW5nIGFuZCBBbmFseXRpY2FsIEZ1bmN0aW9ucwoKV2luZG93IGZ1bmN0aW9ucyBzdXBwb3J0IG9wZXJhdGlvbnMgaW1wb3NzaWJsZSB3aXRoIEdST1VQIEJZIGFsb25lLiBSYW5raW5nIGZ1bmN0aW9ucyBhc3NpZ24gc2VxdWVudGlhbCBudW1iZXJzIHRvIHJvd3Mgd2l0aGluIHBhcnRpdGlvbnMuCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259ClNFTEVDVCAKICAgIG5hbWUsCiAgICBkZXBhcnRtZW50LAogICAgc2FsYXJ5LAogICAgUk9XX05VTUJFUigpIE9WRVIgKFBBUlRJVElPTiBCWSBkZXBhcnRtZW50IE9SREVSIEJZIHNhbGFyeSBERVNDKSBhcyBzYWxhcnlfcmFuaywKICAgIFJBTksoKSBPVkVSIChQQVJUSVRJT04gQlkgZGVwYXJ0bWVudCBPUkRFUiBCWSBzYWxhcnkgREVTQykgYXMgcmFua193aXRoX3RpZXMsCiAgICBERU5TRV9SQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQgT1JERVIgQlkgc2FsYXJ5IERFU0MpIGFzIGRlbnNlX3JhbmsKRlJPTSBlbXBsb3llZXM7CmBgYAoKUk9XX05VTUJFUiBhc3NpZ25zIHVuaXF1ZSBzZXF1ZW50aWFsIGludGVnZXJzIGV2ZW4gd2hlbiB2YWx1ZXMgdGllLiBSQU5LIGFzc2lnbnMgdGhlIHNhbWUgcmFuayB0byB0aWVkIHZhbHVlcyBhbmQgc2tpcHMgc3Vic2VxdWVudCByYW5rcy4gREVOU0VfUkFOSyBhc3NpZ25zIHRoZSBzYW1lIHJhbmsgdG8gdGllcyB3aXRob3V0IHNraXBwaW5nIHJhbmtzLiBXaXRoaW4gdGhlIFNhbGVzIGRlcGFydG1lbnQsIENoYXJsaWUgcmFua3MgZmlyc3QgYXQgNzAwMDAsIEJvYiBzZWNvbmQgYXQgNjUwMDAsIGFuZCBBbGljZSB0aGlyZCBhdCA2MDAwMC4KCkxFQUQgYW5kIExBRyBmdW5jdGlvbnMgYWNjZXNzIHZhbHVlcyBmcm9tIG90aGVyIHJvd3MgcmVsYXRpdmUgdG8gdGhlIGN1cnJlbnQgcm93IHdpdGhpbiBhIHBhcnRpdGlvbi4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUIAogICAgbmFtZSwKICAgIGRlcGFydG1lbnQsCiAgICBzYWxhcnksCiAgICBMQUcoc2FsYXJ5KSBPVkVSIChQQVJUSVRJT04gQlkgZGVwYXJ0bWVudCBPUkRFUiBCWSBzYWxhcnkpIGFzIHByZXZfc2FsYXJ5LAogICAgTEVBRChzYWxhcnkpIE9WRVIgKFBBUlRJVElPTiBCWSBkZXBhcnRtZW50IE9SREVSIEJZIHNhbGFyeSkgYXMgbmV4dF9zYWxhcnksCiAgICBzYWxhcnkgLSBMQUcoc2FsYXJ5KSBPVkVSIChQQVJUSVRJT04gQlkgZGVwYXJ0bWVudCBPUkRFUiBCWSBzYWxhcnkpIGFzIGluY3JlYXNlCkZST00gZW1wbG95ZWVzCk9SREVSIEJZIGRlcGFydG1lbnQsIHNhbGFyeTsKYGBgCgojIyBDb21iaW5pbmcgR1JPVVAgQlkgYW5kIFBBUlRJVElPTiBCWQoKUXVlcmllcyBjYW4gY29tYmluZSBib3RoIG9wZXJhdGlvbnMgYnkgdXNpbmcgc3VicXVlcmllcyBvciBjb21tb24gdGFibGUgZXhwcmVzc2lvbnMuIEdST1VQIEJZIGZpcnN0IGFnZ3JlZ2F0ZXMgZGF0YSwgdGhlbiBQQVJUSVRJT04gQlkgcGVyZm9ybXMgd2luZG93IGNhbGN1bGF0aW9ucyBvbiB0aGUgYWdncmVnYXRlZCByZXN1bHRzLiBXZSB3aWxsIGlsbHVzdHJhdGUgdGhpcyB3aXRoIGEgZGlmZmVyZW50IGRhdGFiYXNlLgoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQotLSBDcmVhdGUgdGhlIHRhYmxlIGZpcnN0CkNSRUFURSBUQUJMRSB0ZXN0X3Njb3JlcyAoCiAgICBzdHVkZW50X2lkIElOVEVHRVIsCiAgICBzdHVkZW50X25hbWUgVEVYVCwKICAgIGNsYXNzIFRFWFQsCiAgICB0ZXN0X25hbWUgVEVYVCwKICAgIHNjb3JlIElOVEVHRVIKKTsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259Ci0tIEluc2VydCBzYW1wbGUgZGF0YQpJTlNFUlQgSU5UTyB0ZXN0X3Njb3JlcyBWQUxVRVMKKDEsICdBbGljZScsICdNYXRoJywgJ1Rlc3QxJywgODUpLAooMSwgJ0FsaWNlJywgJ01hdGgnLCAnVGVzdDInLCA5MCksCigxLCAnQWxpY2UnLCAnTWF0aCcsICdUZXN0MycsIDg4KSwKKDIsICdCb2InLCAnTWF0aCcsICdUZXN0MScsIDc4KSwKKDIsICdCb2InLCAnTWF0aCcsICdUZXN0MicsIDgyKSwKKDIsICdCb2InLCAnTWF0aCcsICdUZXN0MycsIDgwKSwKKDMsICdDaGFybGllJywgJ1NjaWVuY2UnLCAnVGVzdDEnLCA5MiksCigzLCAnQ2hhcmxpZScsICdTY2llbmNlJywgJ1Rlc3QyJywgOTUpLAooMywgJ0NoYXJsaWUnLCAnU2NpZW5jZScsICdUZXN0MycsIDk0KSwKKDQsICdEYXZpZCcsICdTY2llbmNlJywgJ1Rlc3QxJywgODgpLAooNCwgJ0RhdmlkJywgJ1NjaWVuY2UnLCAnVGVzdDInLCA5MCksCig0LCAnRGF2aWQnLCAnU2NpZW5jZScsICdUZXN0MycsIDg5KTsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259ClNFTEVDVCAKICAgIHN0dWRlbnRfbmFtZSwKICAgIGNsYXNzLAogICAgYXZnX3Njb3JlLAogICAgUkFOSygpIE9WRVIgKFBBUlRJVElPTiBCWSBjbGFzcyBPUkRFUiBCWSBhdmdfc2NvcmUgREVTQykgYXMgY2xhc3NfcmFuaywKICAgIEFWRyhhdmdfc2NvcmUpIE9WRVIgKFBBUlRJVElPTiBCWSBjbGFzcykgYXMgY2xhc3Nfb3ZlcmFsbF9hdmcKRlJPTSAoCiAgICBTRUxFQ1QgCiAgICAgICAgc3R1ZGVudF9uYW1lLAogICAgICAgIGNsYXNzLAogICAgICAgIEFWRyhzY29yZSkgYXMgYXZnX3Njb3JlCiAgICBGUk9NIHRlc3Rfc2NvcmVzCiAgICBHUk9VUCBCWSBzdHVkZW50X25hbWUsIGNsYXNzCikKT1JERVIgQlkgY2xhc3MsIGNsYXNzX3Jhbms7CmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQotLSBDYWxjdWxhdGUgc3R1ZGVudCBhdmVyYWdlcywgdGhlbiByYW5rIHdpdGhpbiBjbGFzc2VzClNFTEVDVCAKICAgIHN0dWRlbnRfbmFtZSwKICAgIGNsYXNzLAogICAgYXZnX3Njb3JlLAogICAgUkFOSygpIE9WRVIgKFBBUlRJVElPTiBCWSBjbGFzcyBPUkRFUiBCWSBhdmdfc2NvcmUgREVTQykgYXMgY2xhc3NfcmFuaywKICAgIEFWRyhhdmdfc2NvcmUpIE9WRVIgKFBBUlRJVElPTiBCWSBjbGFzcykgYXMgY2xhc3Nfb3ZlcmFsbF9hdmcKRlJPTSAoCiAgICBTRUxFQ1QgCiAgICAgICAgc3R1ZGVudF9uYW1lLAogICAgICAgIGNsYXNzLAogICAgICAgIEFWRyhzY29yZSkgYXMgYXZnX3Njb3JlCiAgICBGUk9NIHRlc3Rfc2NvcmVzCiAgICBHUk9VUCBCWSBzdHVkZW50X25hbWUsIGNsYXNzCikgc3R1ZGVudF9hdmVyYWdlcwpPUkRFUiBCWSBjbGFzcywgY2xhc3NfcmFuazsKYGBgCgpUaGUgc3VicXVlcnkgZmlyc3QgY29sbGFwc2VzIG11bHRpcGxlIHRlc3Qgc2NvcmVzIHBlciBzdHVkZW50IGludG8gYSBzaW5nbGUgYXZlcmFnZSB1c2luZyBHUk9VUCBCWS4gVGhlIG91dGVyIHF1ZXJ5IHRoZW4gYXBwbGllcyB3aW5kb3cgZnVuY3Rpb25zIHRvIHJhbmsgc3R1ZGVudHMgd2l0aGluIHRoZWlyIGNsYXNzZXMgYW5kIGNvbXBhcmUgaW5kaXZpZHVhbCBhdmVyYWdlcyB0byBjbGFzcyBhdmVyYWdlcy4gVGhpcyBwYXR0ZXJuIGlzIGNvbW1vbiBpbiBhbmFseXRpY2FsIHF1ZXJpZXMgcmVxdWlyaW5nIGJvdGggYWdncmVnYXRpb24gYW5kIGNvbXBhcmF0aXZlIGFuYWx5c2lzLgoKIyMgU2FsZXMgQW5hbHlzaXMgRXhhbXBsZQoKQ29uc2lkZXIgYSBzYWxlcyBkYXRhc2V0IGRlbW9uc3RyYXRpbmcgcHJhY3RpY2FsIGFwcGxpY2F0aW9ucyBvZiBjb21iaW5lZCBHUk9VUCBCWSBhbmQgUEFSVElUSU9OIEJZIG9wZXJhdGlvbnMuCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259CkNSRUFURSBUQUJMRSBzYWxlcyAoCiAgICBzYWxlX2lkIElOVEVHRVIsCiAgICBzYWxlc3BlcnNvbiBURVhULAogICAgcmVnaW9uIFRFWFQsCiAgICBzYWxlX2Ftb3VudCBSRUFMLAogICAgc2FsZV9kYXRlIFRFWFQKKTsKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249ZGJjb259CklOU0VSVCBJTlRPIHNhbGVzIFZBTFVFUwooMSwgJ0FsaWNlJywgJ05vcnRoJywgMTAwMCwgJzIwMjQtMDEtMTUnKSwKKDIsICdBbGljZScsICdOb3J0aCcsIDE1MDAsICcyMDI0LTAxLTIwJyksCigzLCAnQm9iJywgJ05vcnRoJywgODAwLCAnMjAyNC0wMS0xOCcpLAooNCwgJ0JvYicsICdOb3J0aCcsIDEyMDAsICcyMDI0LTAxLTIyJyksCig1LCAnQ2hhcmxpZScsICdTb3V0aCcsIDIwMDAsICcyMDI0LTAxLTE2JyksCig2LCAnQ2hhcmxpZScsICdTb3V0aCcsIDE4MDAsICcyMDI0LTAxLTI1JyksCig3LCAnRGF2aWQnLCAnU291dGgnLCAxNTAwLCAnMjAyNC0wMS0xNycpLAooOCwgJ0RhdmlkJywgJ1NvdXRoJywgMTYwMCwgJzIwMjQtMDEtMjMnKTsKYGBgCgpBIGNvbXByZWhlbnNpdmUgYW5hbHlzaXMgcmFua3Mgc2FsZXNwZW9wbGUgd2l0aGluIHJlZ2lvbnMgd2hpbGUgY2FsY3VsYXRpbmcgcmVnaW9uYWwgc3RhdGlzdGljcy4KCmBgYHtzcWwgY29ubmVjdGlvbj1kYmNvbn0KU0VMRUNUIAogICAgc2FsZXNwZXJzb24sCiAgICByZWdpb24sCiAgICB0b3RhbF9zYWxlcywKICAgIFJBTksoKSBPVkVSIChQQVJUSVRJT04gQlkgcmVnaW9uIE9SREVSIEJZIHRvdGFsX3NhbGVzIERFU0MpIGFzIHJlZ2lvbmFsX3JhbmssCiAgICBBVkcodG90YWxfc2FsZXMpIE9WRVIgKFBBUlRJVElPTiBCWSByZWdpb24pIGFzIHJlZ2lvbmFsX2F2ZywKICAgIHRvdGFsX3NhbGVzIC0gQVZHKHRvdGFsX3NhbGVzKSBPVkVSIChQQVJUSVRJT04gQlkgcmVnaW9uKSBhcyBkaWZmX2Zyb21fYXZnLAogICAgUk9VTkQoMTAwLjAgKiB0b3RhbF9zYWxlcyAvIFNVTSh0b3RhbF9zYWxlcykgT1ZFUiAoUEFSVElUSU9OIEJZIHJlZ2lvbiksIDIpIGFzIHBjdF9vZl9yZWdpb25hbF90b3RhbApGUk9NICgKICAgIFNFTEVDVCAKICAgICAgICBzYWxlc3BlcnNvbiwKICAgICAgICByZWdpb24sCiAgICAgICAgU1VNKHNhbGVfYW1vdW50KSBhcyB0b3RhbF9zYWxlcwogICAgRlJPTSBzYWxlcwogICAgR1JPVVAgQlkgc2FsZXNwZXJzb24sIHJlZ2lvbgopIHNhbGVzcGVyc29uX3RvdGFscwpPUkRFUiBCWSByZWdpb24sIHJlZ2lvbmFsX3Jhbms7CmBgYAoKVGhlIGlubmVyIHF1ZXJ5IGFnZ3JlZ2F0ZXMgaW5kaXZpZHVhbCBzYWxlcyBpbnRvIHBlci1zYWxlc3BlcnNvbiB0b3RhbHMgdXNpbmcgR1JPVVAgQlkuIFRoZSBvdXRlciBxdWVyeSB0aGVuIHJhbmtzIHNhbGVzcGVvcGxlIHdpdGhpbiByZWdpb25zIGFuZCBjYWxjdWxhdGVzIGVhY2ggc2FsZXNwZXJzb24ncyBwZXJjZW50YWdlIG9mIHJlZ2lvbmFsIHRvdGFsIHNhbGVzLiBBbGljZSBsZWFkcyB0aGUgTm9ydGggcmVnaW9uIHdpdGggMjUwMCBpbiB0b3RhbCBzYWxlcywgcmVwcmVzZW50aW5nIDU1LjU2IHBlcmNlbnQgb2YgdGhlIHJlZ2lvbmFsIHRvdGFsLCB3aGlsZSBCb2IgdHJhaWxzIHdpdGggMjAwMCByZXByZXNlbnRpbmcgNDQuNDQgcGVyY2VudC4KCiMjIFJ1bm5pbmcgVG90YWxzIGFuZCBXaW5kb3cgRnJhbWVzCgpXaW5kb3cgZnVuY3Rpb25zIHN1cHBvcnQgZnJhbWUgc3BlY2lmaWNhdGlvbnMgZm9yIGNhbGN1bGF0aW5nIHJ1bm5pbmcgYWdncmVnYXRlcy4gVGhlIGZyYW1lIGRlZmluZXMgd2hpY2ggcm93cyByZWxhdGl2ZSB0byB0aGUgY3VycmVudCByb3cgcGFydGljaXBhdGUgaW4gdGhlIGNhbGN1bGF0aW9uLgoKYGBge3NxbCBjb25uZWN0aW9uPWRiY29ufQpTRUxFQ1QgCiAgICBuYW1lLAogICAgZGVwYXJ0bWVudCwKICAgIHNhbGFyeSwKICAgIGhpcmVfZGF0ZSwKICAgIFNVTShzYWxhcnkpIE9WRVIgKAogICAgICAgIFBBUlRJVElPTiBCWSBkZXBhcnRtZW50IAogICAgICAgIE9SREVSIEJZIGhpcmVfZGF0ZQogICAgICAgIFJPV1MgQkVUV0VFTiBVTkJPVU5ERUQgUFJFQ0VESU5HIEFORCBDVVJSRU5UIFJPVwogICAgKSBhcyBjdW11bGF0aXZlX2RlcHRfc2FsYXJ5CkZST00gZW1wbG95ZWVzCk9SREVSIEJZIGRlcGFydG1lbnQsIGhpcmVfZGF0ZTsKYGBgCgpUaGlzIHF1ZXJ5IGNhbGN1bGF0ZXMgYSBydW5uaW5nIHRvdGFsIG9mIHNhbGFyaWVzIHdpdGhpbiBlYWNoIGRlcGFydG1lbnQgb3JkZXJlZCBieSBoaXJlIGRhdGUuIEZvciBlYWNoIGVtcGxveWVlLCB0aGUgcnVubmluZyB0b3RhbCBpbmNsdWRlcyBhbGwgZW1wbG95ZWVzIGhpcmVkIGluIHRoZSBzYW1lIGRlcGFydG1lbnQgdXAgdG8gYW5kIGluY2x1ZGluZyB0aGUgY3VycmVudCBlbXBsb3llZSdzIGhpcmUgZGF0ZS4gVGhlIGZyYW1lIGNsYXVzZSAiUk9XUyBCRVRXRUVOIFVOQk9VTkRFRCBQUkVDRURJTkcgQU5EIENVUlJFTlQgUk9XIiBzcGVjaWZpZXMgdGhpcyBiZWhhdmlvciBleHBsaWNpdGx5LgoKIyMgRGVjaXNpb24gRnJhbWV3b3JrCgpDaG9vc2UgR1JPVVAgQlkgd2hlbiBjcmVhdGluZyBzdW1tYXJ5IHJlcG9ydHMgdGhhdCByZXF1aXJlIG9uZSByb3cgcGVyIGdyb3VwLCB3aGVuIHJlZHVjaW5nIHJlc3VsdCBzZXRzIHRocm91Z2ggYWdncmVnYXRpb24sIG9yIHdoZW4gYnVpbGRpbmcgZGFzaGJvYXJkcyB3aXRoIGFnZ3JlZ2F0ZSBzdGF0aXN0aWNzLiBUaGUgb3BlcmF0aW9uIGlzIGFwcHJvcHJpYXRlIHdoZW4gZGV0YWlsLWxldmVsIGRhdGEgaXMgdW5uZWNlc3NhcnkgaW4gdGhlIG91dHB1dC4KCkNob29zZSBQQVJUSVRJT04gQlkgd2hlbiBwcmVzZXJ2aW5nIGRldGFpbCByb3dzIHdoaWxlIGFkZGluZyBhZ2dyZWdhdGUgY29udGV4dCwgd2hlbiByYW5raW5nIHJvd3Mgd2l0aGluIGdyb3Vwcywgd2hlbiBjb21wYXJpbmcgaW5kaXZpZHVhbCB2YWx1ZXMgdG8gZ3JvdXAgc3RhdGlzdGljcywgb3Igd2hlbiBjYWxjdWxhdGluZyBydW5uaW5nIHRvdGFscyBhbmQgbW92aW5nIGF2ZXJhZ2VzLiBXaW5kb3cgZnVuY3Rpb25zIGV4Y2VsIGF0IGFuYWx5dGljYWwgcXVlcmllcyByZXF1aXJpbmcgYm90aCBkZXRhaWwgYW5kIHN1bW1hcnkgaW5mb3JtYXRpb24gc2ltdWx0YW5lb3VzbHkuCgpDb21iaW5lIGJvdGggb3BlcmF0aW9ucyB3aGVuIGZpcnN0IGFnZ3JlZ2F0aW5nIGRhdGEgdGhlbiBwZXJmb3JtaW5nIGNvbXBhcmF0aXZlIGFuYWx5c2lzIG9uIHRoZSBhZ2dyZWdhdGVzLCB3aGVuIHJhbmtpbmcgYWdncmVnYXRlZCBncm91cHMsIG9yIHdoZW4gY2FsY3VsYXRpbmcgcGVyY2VudGFnZXMgb2YgZ3JvdXAgdG90YWxzIGZyb20gYWxyZWFkeS1hZ2dyZWdhdGVkIGRhdGEuCgojIyBDb21tb24gRXJyb3JzCgpBdHRlbXB0aW5nIHRvIHNlbGVjdCBub24tYWdncmVnYXRlZCBjb2x1bW5zIHdpdGhvdXQgaW5jbHVkaW5nIHRoZW0gaW4gR1JPVVAgQlkgY2F1c2VzIGVycm9ycy4gRXZlcnkgY29sdW1uIGluIHRoZSBTRUxFQ1QgY2xhdXNlIG11c3QgZWl0aGVyIGFwcGVhciBpbiBHUk9VUCBCWSBvciBiZSB3cmFwcGVkIGluIGFuIGFnZ3JlZ2F0ZSBmdW5jdGlvbi4KCmBgYCBzcWwKLS0gRXJyb3I6IG5hbWUgbXVzdCBiZSBncm91cGVkIG9yIGFnZ3JlZ2F0ZWQKU0VMRUNUIGRlcGFydG1lbnQsIG5hbWUsIEFWRyhzYWxhcnkpIAogIEZST00gZW1wbG95ZWVzIAogR1JPVVAgQlkgZGVwYXJ0bWVudDsKCi0tIENvcnJlY3Q6IGluY2x1ZGUgbmFtZSBpbiBHUk9VUCBCWQpTRUxFQ1QgZGVwYXJ0bWVudCwgbmFtZSwgQVZHKHNhbGFyeSkgCiAgRlJPTSBlbXBsb3llZXMgCiBHUk9VUCBCWSBkZXBhcnRtZW50LCBuYW1lOwpgYGAKCldoZW4gY29tYmluaW5nIEdST1VQIEJZIGFuZCBQQVJUSVRJT04gQlksIGVuc3VyZSBwYXJ0aXRpb24gY29sdW1ucyBleGlzdCBpbiB0aGUgZ3JvdXBlZCByZXN1bHQgc2V0LiBJZiBHUk9VUCBCWSBjb2xsYXBzZXMgYXdheSBhIGNvbHVtbiwgdGhhdCBjb2x1bW4gY2Fubm90IGJlIHVzZWQgaW4gUEFSVElUSU9OIEJZLgoKYGBgIHNxbAotLSBFcnJvcjogZW1wbG95ZWVfaWQgZ3JvdXBlZCBhd2F5IGJ1dCBuZWVkZWQgZm9yIHBhcnRpdGlvbgpTRUxFQ1QgZGVwYXJ0bWVudCwgU1VNKHNhbGFyeSksCiAgICAgICBSQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGVtcGxveWVlX2lkIE9SREVSIEJZIHNhbGFyeSkKICBGUk9NIGVtcGxveWVlcyBHUk9VUCBCWSBkZXBhcnRtZW50OwoKLS0gQ29ycmVjdDogcGFydGl0aW9uIGJ5IHdoYXQgcmVtYWlucyBhZnRlciBncm91cGluZwpTRUxFQ1QgZGVwYXJ0bWVudCwgU1VNKHNhbGFyeSkgYXMgdG90YWxfc2FsYXJ5LAogICAgICAgUkFOSygpIE9WRVIgKE9SREVSIEJZIFNVTShzYWxhcnkpIERFU0MpIGFzIGRlcHRfcmFuawogIEZST00gZW1wbG95ZWVzIEdST1VQIEJZIGRlcGFydG1lbnQ7CmBgYAoKRXhwZWN0aW5nIFBBUlRJVElPTiBCWSB0byByZWR1Y2Ugcm93IGNvdW50cyBpcyBhIGNvbW1vbiBtaXNjb25jZXB0aW9uLiBXaW5kb3cgZnVuY3Rpb25zIGFsd2F5cyBwcmVzZXJ2ZSB0aGUgaW5wdXQgcm93IGNvdW50IHVubGVzcyBESVNUSU5DVCBvciBhZGRpdGlvbmFsIGZpbHRlcmluZyBpcyBhcHBsaWVkLgoKIyMgUGVyZm9ybWFuY2UgQ29uc2lkZXJhdGlvbnMKCldpbmRvdyBmdW5jdGlvbnMgdHlwaWNhbGx5IHBlcmZvcm0gYmV0dGVyIHRoYW4gZXF1aXZhbGVudCBzZWxmLWpvaW5zIG9yIGNvcnJlbGF0ZWQgc3VicXVlcmllcy4gTW9kZXJuIHF1ZXJ5IG9wdGltaXplcnMgcmVjb2duaXplIHdpbmRvdyBmdW5jdGlvbiBwYXR0ZXJucyBhbmQgZ2VuZXJhdGUgZWZmaWNpZW50IGV4ZWN1dGlvbiBwbGFucy4gSG93ZXZlciwgcGFydGl0aW9uaW5nIG9uIHVuaW5kZXhlZCBjb2x1bW5zIG9yIG9yZGVyaW5nIGJ5IGV4cHJlc3Npb25zIGNhbiBkZWdyYWRlIHBlcmZvcm1hbmNlIG9uIGxhcmdlIGRhdGFzZXRzLgoKQ29uc2lkZXIgY3JlYXRpbmcgaW5kZXhlcyBvbiBjb2x1bW5zIHVzZWQgaW4gUEFSVElUSU9OIEJZIGFuZCBPUkRFUiBCWSBjbGF1c2VzIGZvciBsYXJnZSB0YWJsZXMuIFdoZW4gY29tYmluaW5nIEdST1VQIEJZIGFuZCBQQVJUSVRJT04gQlksIHRoZSBpbml0aWFsIGFnZ3JlZ2F0aW9uIHJlZHVjZXMgdGhlIGRhdGFzZXQgc2l6ZSBiZWZvcmUgd2luZG93IGNhbGN1bGF0aW9ucywgb2Z0ZW4gaW1wcm92aW5nIG92ZXJhbGwgcXVlcnkgcGVyZm9ybWFuY2UgY29tcGFyZWQgdG8gd2luZG93IGZ1bmN0aW9ucyBvbiByYXcgZGF0YS4KCk11bHRpcGxlIHdpbmRvdyBmdW5jdGlvbnMgd2l0aCBpZGVudGljYWwgUEFSVElUSU9OIEJZIGFuZCBPUkRFUiBCWSBjbGF1c2VzIGNhbiBzaGFyZSBjb21wdXRhdGlvbi4gTW9kZXJuIG9wdGltaXplcnMgcmVjb2duaXplIHRoaXMgcGF0dGVybiBhbmQgYXZvaWQgcmVkdW5kYW50IHNvcnRpbmcgYW5kIHBhcnRpdGlvbmluZyBvcGVyYXRpb25zLgoKYGBgIHNxbAotLSBFZmZpY2llbnQ6IHNhbWUgd2luZG93IHNwZWNpZmljYXRpb24gcmV1c2VkClNFTEVDVCAKICAgIG5hbWUsCiAgICBzYWxhcnksCiAgICBSQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQgT1JERVIgQlkgc2FsYXJ5IERFU0MpIGFzIHJhbmssCiAgICBERU5TRV9SQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQgT1JERVIgQlkgc2FsYXJ5IERFU0MpIGFzIGRlbnNlX3JhbmssCiAgICBST1dfTlVNQkVSKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGRlcGFydG1lbnQgT1JERVIgQlkgc2FsYXJ5IERFU0MpIGFzIHJvd19udW0KRlJPTSBlbXBsb3llZXM7CmBgYAoKVW5kZXJzdGFuZGluZyB0aGUgZGlzdGluY3Rpb24gYmV0d2VlbiBHUk9VUCBCWSBhZ2dyZWdhdGlvbiBhbmQgUEFSVElUSU9OIEJZIHdpbmRvdyBmdW5jdGlvbnMsIHRoZWlyIGV4ZWN1dGlvbiBvcmRlciwgYW5kIHRoZWlyIGFwcHJvcHJpYXRlIHVzZSBjYXNlcyBlbmFibGVzIGNvbnN0cnVjdGlvbiBvZiBzb3BoaXN0aWNhdGVkIGFuYWx5dGljYWwgcXVlcmllcyB0aGF0IGVmZmljaWVudGx5IGFuc3dlciBjb21wbGV4IGJ1c2luZXNzIHF1ZXN0aW9ucy4KCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRiRGlzY29ubmVjdChkYmNvbikKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIEZpbGVzICYgUmVzb3VyY2VzCgpgYGB7ciB6aXBGaWxlcywgZWNobz1GQUxTRX0KemlwTmFtZSA9IHNwcmludGYoIkxlc3NvbkZpbGVzLSVzLSVzLnppcCIsIAogICAgICAgICAgICAgICAgIHBhcmFtcyRjYXRlZ29yeSwKICAgICAgICAgICAgICAgICBwYXJhbXMkbnVtYmVyKQoKdGV4dEFMaW5rID0gcGFzdGUwKCJBbGwgRmlsZXMgZm9yIExlc3NvbiAiLCAKICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LCIuIixwYXJhbXMkbnVtYmVyKQoKIyBkb3dubG9hZEZpbGVzTGluaygpIGlzIGluY2x1ZGVkIGZyb20gX2luc2VydDJEQi5SCmtuaXRyOjpyYXdfaHRtbChkb3dubG9hZEZpbGVzTGluaygiLiIsIHppcE5hbWUsIHRleHRBTGluaykpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBOb3RlcwoKU1FMaXRlIGFkZGVkIGNvbXByZWhlbnNpdmUgd2luZG93IGZ1bmN0aW9uIHN1cHBvcnQgaW5jbHVkaW5nIFBBUlRJVElPTiBCWSBpbiB2ZXJzaW9uIDMuMjUuMCByZWxlYXNlZCBpbiBTZXB0ZW1iZXIgMjAxOC4gQW55IG1vZGVybiBTUUxpdGUgaW5zdGFsbGF0aW9uIHN1cHBvcnRzIHRoZXNlIGZlYXR1cmVzLiBDaGVjayB0aGUgdmVyc2lvbiB3aXRoIGBTRUxFQ1Qgc3FsaXRlX3ZlcnNpb24oKTtgIHRvIGNvbmZpcm0gY29tcGF0aWJpbGl0eS4gVmVyc2lvbnMgcHJpb3IgdG8gMy4yNS4wIHJlcXVpcmUgY29tcGxleCB3b3JrYXJvdW5kcyB1c2luZyBzZWxmLWpvaW5zIGFuZCBzdWJxdWVyaWVzIHRoYXQgYXJlIGJvdGggaW5lZmZpY2llbnQgYW5kIGRpZmZpY3VsdCB0byBtYWludGFpbi4KCiMjIEFja25vd2xlZGdlbWVudHMKCkNsYXVkZSA0LjYgKFNvbm5ldCkgd2FzIHVzZWQgaW4gdGhlIHByZXBhcmF0aW9uIG9mIHRoaXMgbGVzc29uLgoKIyMgRXJyYXRhCgpbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uCg==