From ee1b7eb600b88d437b8798745ac29d7f71164066 Mon Sep 17 00:00:00 2001
From: Oliver
case class UnresolvedRelation( nameElements: Seq[String], @@ -224,7 +224,7 @@ case class ____(____) Note that AttributeSet is a subclass of Seq[Attribute]. In general, the output field should be given as a sequence of AttributeReferences (see above). -Expression
+Expression
case class UnresolvedStar(target: Option[Seq[String]])diff --git a/src/teaching/cse-562/2021sp/index.erb b/src/teaching/cse-562/2021sp/index.erb index 9d453a19..8dd9d4b7 100644 --- a/src/teaching/cse-562/2021sp/index.erb +++ b/src/teaching/cse-562/2021sp/index.erb @@ -14,26 +14,31 @@ schedule: - date: "Feb. 9" topic: "Relational Algebra + Spark" materials: + lecture: https://youtu.be/xnJNTTirgoY slides: slide/2021-02-09-RA-Basics-and-Spark.html - date: "Feb. 11" topic: "Relational Algebra Equivalence Rules" materials: + lecture: https://youtu.be/IJLLCB6tdCk slides: slide/2021-02-11-RA-Equivs.html - date: "Feb. 16" topic: "Algorithms, Checkpoint 1" due: "Checkpoint 0" materials: checkpoint1: "checkpoint1.html" + slides: slide/2021-02-16-Checkpoint1.html - date: "Feb. 18" - topic: "Extended Relational Algebra" + topic: "Relational Algebra Algorithms" + materials: + slides: slide/2021-02-18-QueryAlgorithms.html - date: "Feb. 23" - topic: "Physical Data Layout" + topic: "Extended Relational Algebra" - date: "Feb. 25" - topic: "Indexes: Tree-Based" + topic: "Physical Data Layout" - date: "Mar. 2" - topic: "Indexes: Hash, View-Based" + topic: "Indexes: Tree-Based, Hash" - date: "Mar. 4" - topic: "Indexes: Modern" + topic: "Indexes: View-Based, Modern" - date: "Mar. 9" topic: "Spark's Optimizer + Checkpoint 2" due: "Checkpoint 1" diff --git a/src/teaching/cse-562/2021sp/slide/2021-02-16-Checkpoint1.erb b/src/teaching/cse-562/2021sp/slide/2021-02-16-Checkpoint1.erb new file mode 100644 index 00000000..63fe996d --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/2021-02-16-Checkpoint1.erb @@ -0,0 +1,785 @@ +--- +template: templates/cse4562_2021_slides.erb +title: "Checkpoint 1" +date: February 16, 2021 +textbook: "Ch. 16.1" +--- + + ++ ++ +Checkpoint 1
+ + ++ + +Checkpoint 1
++
+- Your code is compiled just the same as in Checkpoint 0.
+- Print a prompt '$>' at the start and after each command.
+- Read one command per line.
+- CREATE TABLE statements tell you the schema of each table.
+- Data lives in a '|'-separated file in 'data/[tablename].data'
+- Print query results '|'-separated
++ +Setup
+ + Add this to your build.sbt (modify as appropriate for your IDE) + ++ ++ resolvers += "MimirDB" at "https://maven.mimirdb.info/" + libraryDependencies += "edu.buffalo.cse.odin" %% "catalyzer" % "3.0" +
Docs: https://doc.odin.cse.buffalo.edu/catalyzer/
+Code: https://gitlab.odin.cse.buffalo.edu/okennedy/catalyzer
+ ++ + ++ + + ++CREATE TABLE PLAYERS( + ID string, + FIRSTNAME string, + LASTNAME string, + FIRSTSEASON int, + LASTSEASON int, + WEIGHT int, + BIRTHDATE date +); + +SELECT FIRSTNAME, LASTNAME, WEIGHT, BIRTHDATE +FROM PLAYERS WHERE WEIGHT>200;
+ + + +++import org.apache.spark.sql.execution.SparkSqlParser +import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan + +... + def parseSql(sql: String): LogicalPlan = + new SparkSqlParser().parsePlan(sql) +... +
+ + + +++... + plan match { + case c:CreateTableStatement => + /* do something with c.name, c.tableSchema */ + case _ => + /* Interpret plan like a query */ + } +... +
+ + ++↓ +CREATE TABLE PLAYERS( + ID string, + FIRSTNAME string, + LASTNAME string, + FIRSTSEASON int, + LASTSEASON int, + WEIGHT int, + BIRTHDATE date +);
There is a table named "PLAYERS"... +
+
+ +- ... with 7 attributes
+- ... who's attributes have the given types
+- ... with data in the file "data/PLAYERS.data"
++ + ++ABDELAL01|Alaa|Abdelnaby|1990|1994|240|1968-06-24 +ABDULKA01|Kareem|Abdul-jabbar|1969|1988|225|1947-04-16 +ABDULMA01|Mahmo|Abdul-rauf|1990|2000|162|1969-03-09 +ABDULTA01|Tariq|Abdul-wahad|1997|2002|223|1974-11-03 +ABDURSH01|Shareef|Abdur-rahim|1996|2007|225|1976-12-11 +ABERNTO01|Tom|Abernethy|1976|1980|220|1954-05-06 +ABRAMJO01|John|Abramovic|1946|1947|195|1919-02-09 +ACKERAL01|Alex|Acker|2005|2008|185|1983-01-21 +ACKERDO01|Donald|Ackerman|1953|1953|183|1930-09-04 +ACRESMA01|Mark|Acres|1987|1992|220|1962-11-15 +ACTONCH01|Charles|Acton|1967|1967|210|1942-01-11 +...++ + +Example Queries
++
+- SELECT A, B, ... FROM R (Project)
+- SELECT A, B, ... FROM R WHERE ... (Project+Filter)
+- SELECT A+B AS C, ... FROM R (Map)
+- SELECT A+B AS C, ... FROM R WHERE ... (Map+Filter)
+- SELECT SUM(A+B) AS C, ... FROM R (Aggregate)
+- SELECT SUM(A+B) AS C, ... FROM R WHERE ... (Aggregate+Filter)
++ + + + +Spark's Workflow
+ ++
+- Analysis
+- Optimization
+- Physical Planning
+- Code Generation
+- Execution
++ + ++ + ++ + +Analysis
+ ++
+++- Resolution
+- +
++
+- Replace placeholder values from parsing.
+- "Wire up" attributes between operators.
+++- Validation
+- +
++
+- Ensure all of the types line up.
++ + +Placeholders
+ +++ case class UnresolvedRelation( + nameElements: Seq[String], + options: CaseInsensitiveStringMap, + isStreaming: Boolean + ) +
Separation of concerns: The parser doesn't know what tables have been defined.
++ + +Try It
++ ↓ ++ println( + parser.parsePlan("SELECT * FROM R").treeString + ) +
++'Project [*] ++- 'UnresolvedRelation [R], [], false +
+ + ++ ++ Project(Seq(UnresolvedStar(None)), + UnresolvedRelation(Seq("R"), CaseInsensitiveStringMap.empty, false) + ) +
The interesting thing here is the nameElements field
+ +
(Seq("R") above)This is a sequence to handle multipart names
+
(e.g., source.table→ Seq("source", "table"))+ + +Replacing Placeholders
++ ++ import org.apache.spark.sql.catalyst.analysis.UnresolvedRelation + ... + plan.transform { + case UnresolvedRelation(nameElements, _, _) => ??? + } +
By the way, ??? is valid Scala.
+ +
It means "I haven't implemented this yet".So what goes there?
++ + +Suggested Approach
+++ import org.apache.spark.sql.catalyst.plans.logical.LeafNode + import org.apache.spark.sql.catalyst.expressions.AttributeSequence + + class Table( /* parameters */ ) extends LeafNode + { + def output: AttributeSequence = ??? + } +
+ + +Expressions
++ + +org.apache.spark.sql.catalyst.analysis.UnresolvedStar
+↓
+Seq(AttributeReference)
+ +org.apache.spark.sql.catalyst.analysis.UnresolvedAttribute
+↓
+AttributeReference
++ + ++ ++ case class AttributeReference( + name: String, + dataType: DataType, + nullable: Boolean = true, + override val metadata: Metadata = Metadata.empty + )( + val exprId: ExprId = NamedExpression.newExprId, + val qualifier: Seq[String] = Seq.empty[String] + ) extends Attribute with Unevaluable { ... } +
Simple Constructor: AttributeReference(name, dt)()
+ExprId Constructor: AttributeReference(name, dt)(id)
++ + +exprId
+ ++ ++ AttributeReference("a", IntType)().equals( + AttributeReference("a", IntType)()) +
returns false
+ +Spark uses exprId equivalence to check whether two attributes are the same.
++ + +exprId
+ ++ ++ val id = NamedExpression.newExprId + AttributeReference("a", IntType)(id).equals( + AttributeReference("a", IntType)(id)) +
returns true
++ + +qualifiers
+ ++ ++ AttributeReference("foo", IntType)( + qualifier = Seq("bar") + ) +
represents bar.foo
+ +You don't need to use this, but Spark already uses it, and it helps during analysis.
++ + +Why Do Analysis?
+ ++
+- Makes output work automagically on all existing LogicalPlan nodes.
+- Makes dataType work automagically on all existing Expression nodes.
+- Makes it easier to support eval on all existing Expression nodes.
++ + ++ + +Evaluating Expressions
+ ++ ++ import org.apache.spark.sql.catalyst.InternalRow + ... + def eval(input: InternalRow): Any = ??? +
Input: An InternalRow
+Output: The result of evaluating the expression
+ +eval is implemented for most existing Spark Expression nodes.
++ + ++ ++ case class AttributeReference( + name: String, + dataType: DataType, + nullable: Boolean = true, + override val metadata: Metadata = Metadata.empty + )( + val exprId: ExprId = NamedExpression.newExprId, + val qualifier: Seq[String] = Seq.empty[String] + ) extends Attribute with Unevaluable { ... } +
Unevaluable, huh?
++ + +++ abstract class InternalRow extends SpecializedGetters with Serializable { + boolean getBoolean(int ordinal); + byte getByte(int ordinal); + short getShort(int ordinal); + int getInt(int ordinal); + long getLong(int ordinal); + float getFloat(int ordinal); + double getDouble(int ordinal); + ... + } +
InternalRow is basically just an Array[Any]
+AttributeReference doesn't know which position the attribute will be at.
++ +Suggested Approach
+ +Make your own RowLookup subclass of Expression
+ +Why not use this class instead of AttributeReference in the first place?
+ +Once we start optimizing, optimization rules (e.g., Projection Pushdown) can change an attribute's position.
++ + ++ + +Evaluating LogicalPlan nodes
+ +Separation of concerns: Look at each LogicalPlan node individually.
+ +Naive approach: Compute the full result.
++ + +++ def eval(plan: LogicalPlan): Seq[InternalRow] = + plan match { + case Project(targets, child) => + evalProject(targets, eval(child)) + case Filter(condition, child) => + evalFilter(targets, eval(child)) + ... + } + + def evalProject(targets: Seq[Expression], + table: Seq[InternalRow]): Seq[InternalRow] = ??? + def evalFilter(condition: Expression, + table: Seq[InternalRow]): Seq[InternalRow] = ??? + ... +
+ + +Basic Mindset
+ +++ r = readCSVFile("R") + + s = readCSVFile("S") + + temp1 = evalCrossProduct(r, s) + + temp2 = evalFilter({R.B=S.B AND S.C=10}, + temp1) + + result = evalProject(Seq( {R.A} ), + temp2) +
+ + +Select
+ ++ $$\sigma_{A \neq 3} R$$ +
++
++ A B + 1 2 + 3 4 + 5 6 + + +Select
+ +++ def evalFilter(condition: Expression, input: Seq[InternalRow]) = + input.filter { row => + condition.eval(row).asInstanceOf[Boolean] + } +
(All-At-Once)
++ + +Problem: A "table" can get very very big.
++ +Better Idea: Iterators
++
+- hasNext()
+- Returns true if there are more rows to return
+- next()
+- Returns the next row
+- reset()
+- Resets the iterator back to the first row
+All "functions" can be implemented as iterators that use constant space
++ + +Select
+ ++ $$\sigma_{A \neq 3} R$$ +
++
++ A B + getNext()
for row in input:
+ 1 2 return row;
+ getNext()
for row in input:
+ 3 4 X + 5 6 return row;
+ getNext()
for row in input:
+ None
return None;
+ + +Hint: Scala makes working with iterators very easy
++ + +Joins
++ +Example: Join (Naive)
++ ++ for r in R: + for s in S: + emit(merge(r, s)) +
Project challenge: Implement this as an iterator
++ +'|'-separated Value File Suggestions
++
+- Use Scala's scala.io.Source's lineIterator() method
+- Use String's split() to separate fields.
+- Parse everything upfront
+- Iterate over InternalRow
+- Use InternalRow.fromSeq to create rows.
+- For Codd's sake, don't store entire tables in memory
++ ++
++ SQL Type Spark type Scala Type + string StringType UTF8String + int IntType Integer + float FloatType Float + decimal DoubleType Double + date DateType java.time.Date it's org.apache.spark.unsafe.types.UTF8String
++ + +Questions?
++ + diff --git a/src/teaching/cse-562/2021sp/slide/2021-02-18-QueryAlgorithms.erb b/src/teaching/cse-562/2021sp/slide/2021-02-18-QueryAlgorithms.erb new file mode 100644 index 00000000..50fa456d --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/2021-02-18-QueryAlgorithms.erb @@ -0,0 +1,489 @@ +--- +template: templates/cse4562_2021_slides.erb +title: "Algorithms for Queries" +date: February 18, 2021 +textbook: "Ch. 15.1-15.5, 16.7" +--- + + + + +Next time...
+ +Algorithms for Basic RA
++ + ++ + +Query Evaluation Styles
+ ++
+- All-At-Once (Collections)
+- Bottom-up, one operator at a time.
+ +- Volcano-Style (Iterators)
+- Operators "request" one tuple at a time from children.
+ +- Push-Style (Buffers)
+- Operators continuously produce/consume tuples.
++ + +Analyzing Volcano Operators
+ ++
+ +- Memory Bounds
+- Disk IO Used
+- CPU Used
+Databases are usually IO- or Memory-bound
++ + +Memory Bounds
+ ++
+ +- Constant
+- Scales with output
+- Scales with part of the input
+- Worse
+Core Question: Do we have enough memory to use this operator?
++ +Disk IO
+ +IO measured in:
++
+ +- Number of Tuples
+- Number of Data Pages (absolute size)
+++Accounting
+Figure out the cost of each individual operator.
+Only count the number of IOs added by each operator.
++ + +Note
+ +We'll be discussing the "default" algorithm for each operator.
+ +Often, there are many algorithms, some of which cover multiple operators.
+ +This is why Spark has a PhysicalPlan
+ +In the suggested Iterator-based approach the Iterators are your PhysicalPlan
++ + ++ + +Table Scan ($R$)
++
+- Memory Required?
+- Constant!
+ +- IOs added?
+- $|R|$ tuples read
++ +Select ($\sigma(R)$)
+ ++ + +Select ($\sigma(R)$)
++
+- Memory Required?
+- Constant!
+ +- IOs added?
+- None! (Can "inline" into cost of $R$)
++ + +Example
+ +Example, assume $R$ is 100 tuples.
+ +How many IOs do we need to compute $Q := R$
+How many IOs do we need to compute $Q := \sigma(R)$
++ +Project ($\pi(R)$)
+ ++ + +Project ($\pi(R)$)
++
+- Memory Required?
+- Constant!
+ +- IOs added?
+- None!
++ + + +Example
+ +Example, assume $R$ is 100 tuples.
+ +How many IOs do we need to compute $Q := \pi(R)$
+How many IOs do we need to compute $Q := \pi(\sigma(R))$
+ +Projection and Selection do not add IO.
++ +Union ($R \cup S$)
+ ++ + +Union ($R \cup S$)
++
+- Memory Required?
+- Constant!
+ +- IOs added?
+- None!
++ +Cross ($R \times S$)
+ ++ +Cross ($R \times S$)
++
+- Memory Required?
+- It depends
+ +- IOs added?
+- It depends
++ + +Cross ($R \times S$)
+How do you "reset" $S$?
+ ++
+- "Materialize" S into memory
+- No extra IOs (but $O(|S|)$ memory)
+- Rerun the entire iterator
+- $(|R|-1) \cdot \texttt{cost}(S)$ extra tuples read
+- "Materialize" S onto disk
+- $|S|$ tuples written
+- $(|R|-1) \cdot |S|$ extra tuples read
+This can get very expensive
++ + +Example
+ +Example, assume $R$ and $S$ are both 100 tuples.
+ +How many IOs do we need to compute $Q := R \cup S$?
++
+- Getting an Iterator on $R$: 100 tuples
+- Getting an Iterator on $S$: 100 tuples
+- Getting an Iterator on $R \cup S$ using the above iterators: 0 extra tuples
++ + +Example
+ +Example, assume $R$ is 20 tuples and $S$ is 100 tuples.
+ +How many IOs do we need to compute $Q := R \times S$?
++
+- Getting an Iterator on $R$: 100 tuples
+- Getting an Iterator on $S$: 20 tuples
+- Getting an Iterator on $R \times S$ using the above iterators:
++ + ++
+ +- Memory: 0 extra tuples
+- Replay: $(|R|-1) \times \texttt{cost}(S) = 19 \times 100 = 1900$ extra tuples
+- Cache: $|R| \times |S| = 20 \times 100 = 2000$ extra tuples
+Best Total Cost $100 + 20 + 1900 = 2020$
++ + +Example
+ +Example, assume $R$ is 20 tuples and $S$ is 100 tuples,
+ +
and $c$ filters out 90% of tuples.How many IOs do we need to compute $Q := R \times \sigma_c(R \times S)$
++
+- Getting an Iterator on $\sigma_c(R \times S)$: 2020 tuples
+- Getting an Iterator on $R$: 20 tuples
+- Getting an Iterator on $R \times \sigma_c(R \times S)$ using the above iterators:
++ + ++
+ +- Memory: 0 extra tuples
+- Replay: $(|R|-1) \times \texttt{cost}(\sigma_c(R \times S)) = 19 \times 2020 = 38380$ extra tuples
+- Cache: $|R| \times |S| = 20 \times 200 = 4000$ extra tuples
+Best Total Cost $2020 + 20 + 4000 = 6040$
++ +Is there a middle ground?
++ + + ++ + +Nested-Loop Join
+ ++ + +Problem: We need to evaluate
+rhs
iterator
once per record inlhs
+ + +Preloading Data
+ +Better Solution: Load both
+ +lhs
andrhs
records in blocks.++ def apply_cross(lhs, rhs): + result = [] + + while r_block = lhs.take(100): + while s_block = rhs.take(100): + for r in r_block: + for s in s_block: + result += [r + s] + rhs.reset() + + return result +
+ + +Block-Nested Loop Join
+ ++ + +Block-Nested Loop ($R \times S$)
+ +(with $\mathcal B$ as the block size for $R$)
+(and with caching $S$ to disk)
+ ++
+- Memory Required?
+- $O(\mathcal B)$
+ +- IOs added?
+- $|S|$ tuples written.
+- $(\frac{|R|}{\mathcal B} - 1) \cdot |S|$ tuples read.
+In-memory caching is a special case of block-nested loop with $\mathcal B = |S|$
+Does the block size for $R$ matter?
++ +How big should the blocks be?
+ + ++ + ++ + +Cross product is expensive!
+
Can we do better?$\sigma_c(R\times S) \equiv R\bowtie_c S$
++ +Cross Product
+ ++ + +Problem: Naively, any tuple matches any other
++ + +Join Conditions
+ +Solution: First organize the data
++ + + ++ + +Strategies for Implementing $R \bowtie_{R.A = S.A} S$
+ ++
+- In-Memory Index Join (1-pass Hash; Hash Join)
+- Build an in-memory index on one table, scan the other.
+ +- Partition Join (2-pass Hash; External Hash Join)
+- Partition both sides so that tuples don't join across partitions.
+ +- Sort/Merge Join
+- Sort all of the data upfront, then scan over both sides.
++ + +Hash Functions
+ ++
+- A hash function is a function that maps a large data value to a small fixed-size value
++
- Typically is deterministic & pseudorandom
+- Used in Checksums, Hash Tables, Partitioning, Bloom Filters, Caching, Cryptography, Password Storage, …
+- Examples: MD5, SHA1, SHA2
++
- MD5() part of OpenSSL (on most OSX / Linux / Unix)
+- Can map h(k) to range [0,N) with h(k) % N (modulus)
++ + +Hash Functions
+ ++ $$h(X) \mod N$$ + +
+
+ +- Pseudorandom output between $[0, N)$
+- Always the same output for a given $X$
++ + +1-Pass Hash Join
+ ++ + +1-Pass Hash Join
++
+- Limited Queries
+- Only supports join conditions of the form $R.A = S.B$
+ +- Moderate-High Memory
+- Keeps 1 full relation in memory
+ +- Low Added IO Cost
+- Only requires 1 scan over each input.
++ + +Alternative: Build an in-memory tree (e.g., B+Tree) instead of a hash table!
+ ++
+- Limited Queries
+- Also supports $R.A \geq S.B$, $R.A > S.B$
+ +- Moderate-High Memory
+- Keeps 1 full relation in memory
+ +- Low Added IO Cost
+- Only requires 1 scan over each input.
++ + +2-Pass Hash Join
+ ++ + +2-Pass Hash Join
++
+- Limited Queries
+- Only supports join conditions of the form $R.A = S.B$
+ +- Low Memory
+- Never need more than 1 pair of partitions in memory
+ +- High IO Cost
+- $|R| + |S|$ tuples written out
+- $|R| + |S|$ tuples read in
++ + +Why is it important that the hash function is pseudorandom?
++ + + +What if the data is already organized (e.g., sorted) in a useful way?
++ + + +Sort/Merge Join
+ ++ +Sort/Merge Join
++
+- Limited Queries
+- Only supports join conditions of the form $R.A = S.B$
+ +- Low Memory
+- Only needs to keep ~2 rows in memory at a time (not counting sort).
+ +- Low Added IO Cost
+- No added IO! (not counting sort).
++ + diff --git a/src/teaching/cse-562/2021sp/slide/2021-02-25-PhysicalLayout.erb b/src/teaching/cse-562/2021sp/slide/2021-02-25-PhysicalLayout.erb new file mode 100644 index 00000000..a5c87452 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/2021-02-25-PhysicalLayout.erb @@ -0,0 +1,258 @@ +--- +template: templates/cse4562_2021_slides.erb +textbook: "Ch. 13.1-13.7, 15.7, 16.7" +date: February 25, 2021 +title: "Physical Layout & Memory Management" +--- +Next time...
+ +Extended Relational Algebra
++ + + + ++ ++++
++ CREATED_AT TREE_ID BLOCK_ID THE_GEOM TREE_DBH STUMP_DIAM CURB_LOC STATUS HEALTH SPC_LATIN SPC_COMMON STEWARD GUARDS SIDEWALK USER_TYPE PROBLEMS ROOT_STONE ROOT_GRATE ROOT_OTHER TRNK_WIRE TRNK_LIGHT TRNK_OTHER BRNCH_LIGH BRNCH_SHOE BRNCH_OTHE ADDRESS ZIPCODE ZIP_CITY CB_NUM BOROCODE BORONAME CNCLDIST ST_ASSEM ST_SENATE NTA NTA_NAME BORO_CT STATE LATITUDE LONGITUDE X_SP Y_SP + '08/27/2015' 180683 348711 'POINT (-73.84421521958048 40.723091773924274)' 3 0 'OnCurb' 'Alive' 'Fair' 'Acer rubrum' 'red maple' 'None' 'None' 'NoDamage' 'TreesCount Staff' 'None' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' '108-005 70 AVENUE' '11375' 'Forest Hills' 406 4 'Queens' 29 28 16 'QN17' 'Forest Hills' 4073900 'New York' 40.72309177 -73.84421522 1027431.14821 202756.768749 + '09/03/2015' 200540 315986 'POINT (-73.81867945834878 40.79411066708779)' 21 0 'OnCurb' 'Alive' 'Fair' 'Quercus palustris' 'pin oak' 'None' 'None' 'Damage' 'TreesCount Staff' 'Stones' 'Yes' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' '147-074 7 AVENUE' '11357' 'Whitestone' 407 4 'Queens' 19 27 11 'QN49' 'Whitestone' 4097300 'New York' 40.79411067 -73.81867946 1034455.70109 228644.837379 + '09/05/2015' 204026 218365 'POINT (-73.93660770459083 40.717580740099116)' 3 0 'OnCurb' 'Alive' 'Good' 'Gleditsia triacanthos var. inermis' 'honeylocust' '1or2' 'None' 'Damage' 'Volunteer' 'None' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' '390 MORGAN AVENUE' '11211' 'Brooklyn' 301 3 'Brooklyn' 34 50 18 'BK90' 'East Williamsburg' 3044900 'New York' 40.71758074 -73.9366077 1001822.83131 200716.891267 + '09/05/2015' 204337 217969 'POINT (-73.93445615919741 40.713537494833226)' 10 0 'OnCurb' 'Alive' 'Good' 'Gleditsia triacanthos var. inermis' 'honeylocust' 'None' 'None' 'Damage' 'Volunteer' 'Stones' 'Yes' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' '1027 GRAND STREET' '11211' 'Brooklyn' 301 3 'Brooklyn' 34 53 18 'BK90' 'East Williamsburg' 3044900 'New York' 40.71353749 -73.93445616 1002420.35833 199244.253136 + '08/30/2015' 189565 223043 'POINT (-73.97597938483258 40.66677775537875)' 21 0 'OnCurb' 'Alive' 'Good' 'Tilia americana' 'American linden' 'None' 'None' 'Damage' 'Volunteer' 'Stones' 'Yes' 'No' 'No' 'No' 'No' 'No' 'No' 'No' 'No' '603 6 STREET' '11215' 'Brooklyn' 306 3 'Brooklyn' 39 44 21 'BK37' 'Park Slope-Gowanus' 3016500 'New York' 40.66677776 -73.97597938 990913.775046 182202.425999 ↓
++0101010010111010101010001010101001101001001010010001010101001...
+ + ++ + +Record Layouts
+ +How is data stored?
++ + +Problem 1: How should you encode one tuple?
++ + +Record Layout 1: Fixed
+ ++ + +Record Layout 2: Delimiters
+ ++ + +Record Layout 3: Headers
+ ++ + +Record Formats
++
+- Fixed
+- Constant-size fields. Field $i$ at byte $\sum_{j < i} |Field_j|$
+- Delimited
+- Special character or string (e.g.,
+,
) between fields- Header
+- Fixed-size header points to start of each field
+- +
- +
+ + +Problem 2: How should you encode a file of tuples?
++ + +File Formats
++
+- Fixed
+- Constant-size records. Record $i$ at byte $|Record| \times i$
+- Delimited
+- Special character or string (e.g.,
+\r\n
) at record end- Header
+- Index in file points to start of each record
+- Paged
+- Align records to paging boundaries
++ + + ++ + +openclipart.org ++ + ++ + ++
+- File
+- A collection of pages (or records)
+- Page
+- A fixed-size collection of records
+- Page size is usually dictated by hardware.
+
Mem Page $\approx$ 4KB Cache Line $\approx$ 64B- Record
+- One or more fields (for now)
+- Field
+- A primitive value (for now)
++ + +Problem 2.b: How should you store records in a page?
++ + +Goal 1: Where is record $X$?
+Goal 2: Support updates/deletions
++ + +Fixed size records
++ + + +What about variable-size records?
++ + + +Why store the key and records from opposite ends?
++ + + ++ +Problem 3: How should you organize pages in a file?
+Key question: What happens when all records on a page are deleted?
+Idea: Track empty pages.
++ + ++ + ++ + ++ + +An Alternative Layout
++ +Row-Wise Layouts
+ ++ + +Column-Wise Layouts
+ ++ + +Each file stores 2-tuples $\left< RowID, Value\right >$.
+Values only for one attribute.
++ + +Benefits
++
+- Only one attribute to sort per file.
+- No IO cost for unused attributes ($\pi$-pushdown!)
+Drawbacks
++
+- Result attributes must be stitched back together ($\bowtie$)
+Great for wide, rarely-updated tables where only a few attributes are used per-query
++ +Example Column Stores
+ + +By Apache Software Foundation - https://svn.apache.org/repos/asf/cassandra/logo/cassandra.svg, Apache License 2.0, Link +
+ By Ariolica - Own work, CC BY-SA 4.0, Link
+ By Source (WP:NFCC#4), Fair use, Link+ diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-16-RA-Tree.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-16-RA-Tree.svg new file mode 100644 index 00000000..910a0882 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-16-RA-Tree.svg @@ -0,0 +1,150 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Cross.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Cross.svg new file mode 100644 index 00000000..df8d8209 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Cross.svg @@ -0,0 +1,423 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Project.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Project.svg new file mode 100644 index 00000000..dbe2612e --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Project.svg @@ -0,0 +1,252 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Select.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Select.svg new file mode 100644 index 00000000..43e07a21 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Select.svg @@ -0,0 +1,285 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Union.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Union.svg new file mode 100644 index 00000000..26435b5b --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Flow-Union.svg @@ -0,0 +1,372 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-1PassHash.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-1PassHash.svg new file mode 100644 index 00000000..8fa80c73 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-1PassHash.svg @@ -0,0 +1,430 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-2PassHash.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-2PassHash.svg new file mode 100644 index 00000000..d254f98f --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-2PassHash.svg @@ -0,0 +1,608 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-BNLJ.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-BNLJ.svg new file mode 100644 index 00000000..3f28615b --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-BNLJ.svg @@ -0,0 +1,274 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-Grid.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-Grid.svg new file mode 100644 index 00000000..16848069 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-Grid.svg @@ -0,0 +1,368 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-NLJ.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-NLJ.svg new file mode 100644 index 00000000..777c5202 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-NLJ.svg @@ -0,0 +1,263 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-OrderGrid.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-OrderGrid.svg new file mode 100644 index 00000000..8c7c0c45 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-OrderGrid.svg @@ -0,0 +1,353 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-SortMerge.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-SortMerge.svg new file mode 100644 index 00000000..39f1c5e9 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-18-Join-SortMerge.svg @@ -0,0 +1,303 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Buffer-Manager.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Buffer-Manager.svg new file mode 100644 index 00000000..8215b798 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Buffer-Manager.svg @@ -0,0 +1,577 @@ + + + + \ No newline at end of file diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Heap-File-1.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Heap-File-1.svg new file mode 100644 index 00000000..53f2874f --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Heap-File-1.svg @@ -0,0 +1,390 @@ + + + + \ No newline at end of file diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Heap-File-2.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Heap-File-2.svg new file mode 100644 index 00000000..0d791aeb --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Heap-File-2.svg @@ -0,0 +1,269 @@ + + + + \ No newline at end of file diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Layout-ColumnWise.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Layout-ColumnWise.svg new file mode 100644 index 00000000..14aa294f --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Layout-ColumnWise.svg @@ -0,0 +1,473 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Layout-RowWise.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Layout-RowWise.svg new file mode 100644 index 00000000..667af648 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Layout-RowWise.svg @@ -0,0 +1,416 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Page-Layouts-1.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Page-Layouts-1.svg new file mode 100644 index 00000000..e44c25ec --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Page-Layouts-1.svg @@ -0,0 +1,729 @@ + + + + \ No newline at end of file diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Page-Layouts-2.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Page-Layouts-2.svg new file mode 100644 index 00000000..7f1a4b42 --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-Page-Layouts-2.svg @@ -0,0 +1,385 @@ + + + + \ No newline at end of file diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-mem_bulk_loading.svg b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-mem_bulk_loading.svg new file mode 100644 index 00000000..8a8482ee --- /dev/null +++ b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-mem_bulk_loading.svg @@ -0,0 +1,9149 @@ + + + + diff --git a/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-mem_hierarchy.png b/src/teaching/cse-562/2021sp/slide/graphics/2021-02-25-mem_hierarchy.png new file mode 100644 index 0000000000000000000000000000000000000000..6f8534561dc942bcbfdd73e3f41b5f831816fb76 GIT binary patch literal 234215 zcmY(rb6_P+^9CB*+1NHWwylkA+qP}n*l6QqW82!;woiPs@B99~d++(@%+z#ub+ + +Buffer Manager
+Abstract the messy details of File-IO
++ + + +openclipart.org ++ + ++
+- Frame
+- A "slot" managed by the buffer manager that holds one page.
+ +- Pinned Page
+- A page currently in use by part of the database. Must stay in its current frame until unpinned. (A page may be pinned multiple times)
+ +- Dirty Page
+- A page that has been modified since it was last read in.
++ + +When a page is requested
+ +Is the page in the buffer pool? +
+
+- Yes? Pin the page (again) and return the address.
+- No?
++
- Pick a frame for replacement with your favorite algorithm (e.g., LRU)...
+- If the frame is dirty, write it to disk
+- Read requested page into chosen frame
+- Pin the page and return its address
++ + +Does this all sound familiar?
+Isn't this just Virtual Memory?
++ + +Yes!
+(Many databases use memory-mapped files as a buffer manager)
++ + +Why Re-implement VMem?
++ ++Databases can predict the future!
++
SELECT * FROM R WHERE A > 500 AND A < 2000
→ Pages 10-12+ + +How do we decide which pages hold the query results?
+Answers next class!
+H`6Br2_$B*k}L#!SgkchJ~`av%0Jd zmyw+fy@9cvp$Waajr~^-ARr!huCGHI6K4ZLcN=S4CoXqh;(uCjeI5T5GY}L0)5O_| zmsnj^fl%1a(S(qdo`s%~m=A`KkdVjG*py3AMC`xrUuV3;=FZOcTnr3uZf^8$-|6if z%@~+CIXM{^nHiXw>AqUfIeFMR8@SWiI+6T4$p4KaV&Y`vXkqVcVP{MDcU%KQI~QkO zV&cDw{`dKJp3WAg|F0%nr~j7qwLpfyR~VS+85#aJ_E%S)zfvxFM+=iL&40(|W8(Rz z<^NCipB^5DzsmnVllgb0|46@<$_K;4@W0!}2P4x*p#cOW03<0QsO%1Wt^=vBtg`e$ zo-`^$-kB6HL>;^-7^(cj;;{>+#>B>I<+k?h^ygXk@3YgZ)g?3?8cIXZVkn8L90d@f zfOr9ks|=Y$i%(9w+eG%Hj8U*cSVsn#O|Pd7ugkVAud7Y3%g!U*lo$}-zMjDfW$HLG z&!i!e06E)SL?5wA)JJZr_in#e@6%5sM}|8x0>8+5`bTD8nGXh$)s1)C)}rAXTrR81 z-;oKGx7vd)DYj~JpXvwi>dx(oEd(TV%TYm v$|vO`AGYYp4h{V^=UXYR?P@ z9AvIKgYSw%7iBfCC6CtLQ)v2Fx6t;D^7gF}Y9vl=1?asIH++uX7>s_&?%D9*wagvE_j2oPy3?0` zklT?e&7q(fuLj_Vc67Sp@ha<{HKl@;=9%3xy?Ycr&sod(mMw_vf(iu%1_lBG9N;e? zKuCy$^mTd;m7UaM&or$%j`CP?3-Z>IHsMRp!QI8)L20J)Mjxx`h=+T& MDlt2{Q?_L99w7n)u@>7$*zkeRJB^hg$4{=r#P537vFjO$33 zUtas|>56mt?v3*lz%!k9I4ECWw6>#OcIk_-Ip;K(R>>i5d!e@Ji@I6rq%!n8 De0jS_e2r__S0zSkQj#QP}^K?6T*?*~z?#g~}P@ z)KPhMS=ismJFA#_LFczQb4E=kcNI~?e$R8@rZPP>c23P}f;Dv{Q`vCK5y$?JY~j_P z_jPgBVX%RnB|}`^`GD&>;iQdPJR@{E=cH}?&glARBI?dX&BJN9U1m8W7x&8Bm}^6D zTx+8Bt>*f0qV}#SF?Aw{Id*>hX39fHhx^DSC3LT&U?r!qRmSevqd%>skt3(&{!4&*nN2=7hC*eV9G0gV#|o=+RkX)}*OZ>QWQNe*Zg*-3^li3T`Ue zF(flHb7p=0{HA0Zree|Z_`wtK^75ND%0i14orR69&zh~J#DPA^j*&M;EU2far`dUm zB754HxuBq+_gOW}>X43*uqS~1$np7iO^w`c9VoCU7%c1r&b(7;fdsjMDI;d`4}>Fa z@u}(iz@Q*r7^3W&t}fl(m}y?{YE_z>Ln?TFd0&K_S;I{Lmb+)EHp1S}m6X}NGZ!A< zNOF7kY0{j +s^xfp6>48=;)}qMq7TUx}-$Z^9rDeFy)-VUOGJ8 zr{fWgF~3LPhjBT_WaGPK{ISoR6E}e|ziZd0q`wL9V0O(NyMJ6W^nBa+?&=tSvrPiY zcl$cT &iNhuL~YQNthGo1C5cH+RRb3=}5>iK3fdKLcl^W2BBPm%1A zFaP*zTS^4UcVripS6xPUS6aMB&D-Lz9&4|^diykT^cI~QaOJSJu+rY=fywO~m*UOL z;HU%0KDlNezp5+i@&2 GJsW 7^%*lx&aYs}@y^VW-aFTc3| zXd>&atIg9d<)lfI-_s4Ro9@7e@5qe$Gy96Fw#}C-r@3j`;cfH4c>-rS h`)s-2g8odg7e2 z;NVXi?#0>Uz*|n);^fTBPK$HICCI>UA ibK(+;bMzF- z?sj0`(SMwar5xH|Hf^}pTun*IQ4;OyF_nVNYz!q9i!1rW-Sta5W4=s&FUzjWqFQ}; zN`QRAJ2)i7U@E(PgAI2*{bIchRYT+Tz}hyBD+k|bICgNRKzMDA@@y Jk5A}8ZV4;61bIdeQp2KjB6;4K;9wt zV1yHafG=97-Bl7kZ1T`!8yyEndfEVe2mN#x4}zMSnzYOHa-BZO&1=ED5tGFniCi{w zAyw=-_wGqS+dDM3AEg4=PI~_^m>c-xa)UJyLKqYQf1+=xT8j+kQ7ls)nx}6T-vTm` zo9Zy8n}L(#))d2)|LvMz7e2R5sq1Oei)>Swx=E(~V&r1v@nWq)=M J{!Rc@!VO93h-O16he1mehCjgYYBdD#7^H04A?9ZP+ z_YK$L?&;U6ZrjU2{4hGbo{0DutfY$u; j=0q89QGh~FBqGEFHkdT<^wmI9PWAVOYNxVs3P8ZtIH{e$5Lo|uN=a*7v$}` z2d_r9dxg13o5a;Ca@GRZ*Y~!$+|b?OdVSPoWLU^t-feE&7WwEM_#uqe=jP^^JZ`^* z89r~scZ+_=J!_T!{An+-cN&;?Aj`fHSYA%4))FB`7K_JC9>abB7Q2; 4}yk{ZfLGVx@1)5Ikuey@$#y}DRyDn5!(dsIdg3u6!OuaKNQ}31^;-h zFg3AeZH? zlEw6v*54jWI25kG)nRYF+2O*wmCs4tz+SE|k+K`GxVSjg!lm87f{lSe;gdg>C3THO zh!6KWsfvRR(w7JTE-fkfBhiuLNGu$O72T2t93tanaCq2lTQ(z3FLG} KFno<*gT8z=BoR7{lK3A%Ff z#g|A |eoHu%T z%gy0%5aOb?X!}XChmM6s1s!zhZqx t8`1J0i6mODsd}Fa}(AXbR0vCeLFJ;dCP7`(>n6)K^U7HgXF@rr5vA zrGPbFUuEXbx&D4I)GnXLN53U1Hw906r>d?_d60T`AR_g9|M%nh$~=kP+ea>s$6Nrr z8cPPduU%V_;qMxdqrId2{Yf-BPCFr?K$adi@wj=1tN{QMo_v7WIza2-sLuD@v*Y
v;o$MyC&ccElE%Z!@j-Z`k#v8@IC>owU1~*P$!NT3yL|Zylo%Ar$_t zsa2QKQBaj!@9j}Cw}7*;>7ck=5PneGAS_|GfGxDcn5~1U!1W{dzL@TYj*gCIEKT2G zZC &JMy|u2f@)dncOT1IE3h?C!5|H+p$ATOGduints7 z5?|1gQcy@=<+NV~t^s|}1ALTjN$)C(r%@5V#?Op7bZu>H41Qr2-;o%S(g d5Kncl>5^!#-%_lRJXT ZWPJTBWEW8oBd$w_W0l^IRuE%eH zkPBLS;eFX-+s%&NYMKrvznU#%uM5oY&nvE~dUmH{_u1WaYR(xHrNjJgf**1hSK;l- zkM4HvXJg!bS6LLBtqU~^O$}GMXCqrd?7dlT#qVpkG6{M8AtN|@y@2m_Jm%h4oln=7 z?N?Hu)aX)&lUWqiEF(>O_qXk|!)@HmI?wM;X{Fp-yoa)f(>flG9TRC~Zt|sr!&7}B zFWwn;e>JhM zv*A|T+~*DDyEu5g78o-SIP+xOm}=Sc@xthfl5)-LvJ5QB=?Dau8y%O&)rMa!S4z2D z`LyqRE_?g4>Y+fIOnbpGtF)vhWqGuMW-BG7!)ex)+~Go2TiKYy3RZL58r2@}aaN_| ztVZE>BU9UM?#gbvAzRCRHnyee*5$PEZ1cX*!P!F7ffv|fd3#QS$Yu<@oA>! Mv-`p#Q^FBbD_cXvjTve(U5Y)jGovBUXrJ!F!ZOLuAGmeY*Fqfu8wg>J870zDG zEp516J&r{E+OlpZ3JR>$>>nQPwqJGi7YK(HYj}1Iul2ZJU92`)=}c`pa38Fz>M(Cd zM8f>H&0U6S#jNwpS*9Yg(W}oeIIR0!Ca}gxYm0hgp25u+wb0fkdnmFcwuWlXEQ_2T zyEn3CYLQR{%EE@NApWYM)mo&yZ|_1q+r-FUbJ;xfBZp48wT#rdAunxpLL>@9k_TLu z+1T^()P7fv*`1jD68cMR8W;%Av0j5|Oyjg_n_k|YN8Fj3o(~z-fF6o|xYU0W6KEls zIY}*+lWxSERnQ5jpJV=}!roIlN5oaaK4&OgP&y@*3EYsd(zyzwoTw-&s6j`|#|1`V zn(rr+9+W}ePymBg%%i5L8H1e!&ez2iOi@h4N`%XW4n*qFCpRy)gdV+ELdl~NE(J=( z|7?!@4K*qqe>NQIJ}gv}h$bVGWuYMbn}uB3a(;w)eM%^LX46yDS(HHqFH*r1Nqxsg zc?zG&_hls=m`DXh*-{z7*M=ZTCMs$O3x|Shs}x~na!{!#m>=re0b<{w(SAb>3_J*E z%YX^Fn<<@*MOV~-c?`=H_L&E&R0r^pq9D&Q9b=(_KrU{PD>CzD2xGD(Fz!L>C{XSy zB_ZRKC$yd2{|E>BA|-lC0)Sva9C7+tO|48S)oN3drE`$CM`2WhP5Ys(spQ%kYl?TW z<+Wk|$6<)|6!j1?sI*@wTho=(@C54c(+(z@Z0((GnB_0M7)=QK4d-2EMSKwPOi*mW zqERGt`ckRVYJ|G6?F-={g${7%_I7No`**PrHpgAyDExBcO!a=DLuc5EKuw+xv;Owf zd11lxeBFe(_8m9zk{|xhq%3?n&D8LY%hs-*U3ZWynKmBUi%b{Z@`g8jX*6#5K)As1 zDU3p^8SmlytC4pX34t-40!&A<0^f@y?fY4EdLkAmf9^oqWx_+|i ;dw7RL1z$q>ZKSRG_4DA5#!w@XY1$XN;=9E z%xq((ewFCd;q>)+k)2^}f;B+NdZK3(ll4w@-EWN)E)pvKw1yc+A-xHm;PWC5aHvl= z7Tp^zi4)rXc}shVzl;-rpO>gib|e191+@kvDKw9+%hjsa3Dx8FYsF`;*>oKOp6`yN z(O1?p*!G>Jey1U27ykT@J(__A!3!;PEbA3t>4VG4afItI7=m0n1qDnS$?vqk4?AR^ z-$Q~W8X>F}Hr!qC>)WR=$nk#%t>X((zZo8`vp*xUW`9Y5ZYBiG{2Zu=%k4erJ>hts z(GNDSqTKkP!hgTxIiIE>=`__dXYFeADFYAV^Mz50TQEGLKs2;3N3g`!&f8=opy>i2 z`RTZ(PPm*DAa2igAx@oZ1}?Yw1%*6);B{xZa1P >4AR+598 zG{Mki+$76O)8MX4uC~0Z!=e3o-85?bNO74t9(fL>2kQMGmHw6h-J-+4t-c30U;#(D z$wk2azFW&7+&umM=4^~UNkDX1R9?jwu#qB$f(c^b5CoQQ!m#^7fl(Mh`|@lt%7kg4 z hTFk;{VZk z_G0-#J%n!9DJf`)MpQXddAM*fj`c %>0jHeZjL^U) zq-aUu_y^7wsgawm^PzokAMQN39E7jjAx3BO!#q2Q6FPrVs9!%Tb~JQ=^b&PC?AcxR z0^sT|u5cp5kx(sf5smJ!{DRrzJH0hG%u^v&sj ymEUfu1M%iyh7W)%$<<_#?pLmjhiAm5H+!9?G34h_}KL7l?!hohR?&U#QILI>a0GQtS3qIzG;0# zAj5_goQLXrzlLq~FYJfyd+8149m$AE&0?Rw-3s$G&-;IF=m>xalkZHXE17G-*Xc!e z|Ld;&c`}Ns=(L0lQGZ%% {O5*8n(pyNpKN_7Scv{ >yO0;?-akc*Op_UQ K? R?>!B jElbB3Q4N~$SpQ8%!wY>85}HhQBKl+@{Aah9wM|33!>1F|0K zpg%5g)o~}6^=lge!8T0YTtBI@>dbyoMNKjmD93`jc4HRh=07K8e?pXxi@?2@D_&Y7 zUiLUO21Dl}#HK=qt!b%XW9=d>o1 4L)uGx~`53cQ#3XVhvf?!ST?UyGn-_NSP zA%hI_3|?SRk1_;k*y1r6ameqIiXEg93f-sTLc)gW8 !{0M#9sID ztAQ{Lsz!%@GEnCeS4voFhi%CdGR$)gDaQQSNn&z-#D()mE=_F$7*6V*li^- V0m6wH3z=Jx%el?n0PNA->*Ekax1Gwj=$-FzX0Z!nO&(}$BkIlR3JOr zk$1;fPp;Sj8uZkWCCVU jvwuYBHx4twU_NmB#6KJ$hIdNN7bz<*E8za%=`9S?Tf(wg zdzl2*JwQ3|y3$E`hpB-lp%r7EU#-Y$_(Kr7F?lIjV9i&eBEy?qnl}Eml5LYyUP+X% z4V+=1xr)bF4bZ2k78IJ6*WVx>RA+kO+sM)qf1-$U>9yx0gTo4EVf-Rd0xvcIfV`R9 zqIG{8eR3!@;6MDl5(WVJ9nu{xtk*p*+@r(iNmvXxa&taW`g8$!=m%K8J{!`m*T_Bx z_91ioT$F~5ygLxFrR4N~z*6y|dL0Ne$D?dR!b8*HdXLy}+ugww-J#d-Hf~$ wK2yJp?d6Lsky2wHuqmXiz-#L?os&?6#t}de08C&T^H}~`DV^QOLRmp zEsvUdwtos+lp-@tpQ+)m1lWjjj?BO2m(*aZxDcg(ymoK*`XX~wHwo%>!MCg7iNxk% zk;`JzdjTBY9A7x#;z-F3i&PlZ=~!CHy`-?`7|D5o<|I=Cl}iU@jEt#6AjX;Y(+d(} z(KVZ)wrAa~xF8KeO#uyCexO?y9R7=CQa`k0i>)Wk>9-;VrM-V>Ec*NBf-)JtaTE^? z5DwVRgqv54cq)B^nC?KlDvlQ4>~c}RuC4v`3E+CWo<*M1`4gze_rOD*kU!^iPs{V> ziCx3t31x-R8JPlE`RkFtmgv=)X-FO~%8Qnj(H=gWKPnHu%jP%A1ngqS>A0}($Dn3a zd66^Z;XAx&v*)+8q5P`>FZ)G0C5MM{nP5u`v|%|Ibj2z1SxF*}8F=KcJXk;;?-$&p zXz6EVWld+d%Wbr2tV}sd2{{-~BjMuGtk3+0i@UCZLfrYExxdLE=}*sBEu&*gT_PQ? z>)0b#qqp>^{1`O6U|3!3wjS+AIW<5H=&ypAiV|8SsONm+9Z%Q%S&=+K4AFEQ|P} zSz0L+DU#5Wz`uva=L&j8v*<%Si{>9dML5UpQ;LW|wq$aejP?ZnKF=933kXy!cQFX3 z(1SQ2J20Q15#Urr7QJz#usn(bi%Yevm-fwk 5i9Df z&C_=;2IhOi+C|z>4f%u{swcr?i! gr{stPiQM7Xq*M_% zb9weCPXdeb4VT6ZCK84$QVZYUO36+;=l)>{;~4xd^@jG9WAr_QqFieY4TbokBak2w zaF6F)Ny$V?_EF6~Ki-d*YYE6@((^mWrojJAO>UzI`>SPhYl^sl^$P0ScO0BVidR_% zi^P+Bmt~dtaRSx;=E>u3MJ}8BN<>PJV2~JZNZ1KRkj}b4K`SKu0cO4}zZP&heYm}S zLTH>Q%vN1Xz{yCG2P?uvMFvyHM9OGCYaG~E^1{Mar5=LY5TS}@O&U&G;P(rCIC2zu zb@M_!OL{3e(GY2Wxa~k7aqbuL@!E0J?;dT;@o7V|6c`6#v+Y!`tAXNV^WdZ!AUeuM zNLA-jW^4%os5uMsQ@yb81%uW@e81#NB7WHYbXz|oShk~-K8|SIn&%`r@Po%V(t`^P zMB;J|jI4%f;_l*9h>rtC_#zu|Ws3UJ$Y@_)kOY|Lq|sc7b344!~{6!Tr(@&^z4`krP$vS{`kSYjgl}) zV5#F^2IM+)&d>w|gG2rl@cH8om*ozBxbBWMp%QkzlSeQNNh${==-x4Ld4L0!8? jqbsREYS%!3IQMK&udtw;;@CN@(5_h%}A?vi0i zN+PA&o!M5dAm?w&q`zdcqSk6-#jpP8{HZ$!@3?-YxIT$B;!xVEaz{#*b0CxZvflt{ z*npQ8Ph3t%ur1%$3lEiS@X)t6Z|jwMa19NOBT;E%^nY{k0eSo&2sCh;wvvgDv#4wZ zJ}>?9-N1Z<@aCLysj-ywnPQFSk}F+1p-*S?pZ@p~ST4MmZCqgQBe&CbN7X_*?bF+u zC=lr99q<+`QMpPxK{}tl*w6fMJ ?K zxb6I!k&y%+h)=dJ7$BkYy>v=L2KJgTyCnVQtRyHq1w?a%TZcxe9ofF^?wp5@+2Qsm z->@a2f7cg+EG{+`D+y}F2zeu~b$~iudLgb7DcL_Q{5ZzT3+@T^0T@d&Ataw7&4`ET zW_rKfq?Y~2zX+0aDm4(YgxT$z;J99aNJhs&LMjur7lHB~aTER&AlPS9|Ml@j#<;!5 zU}7nT5U^c|I*9TvU@PXzN%i{ceG**CsRyM&7bONKwC<#_8=jYS_ZA*-|6+c{{J0sW zV7!{@CVZ`{GsaI{##m(|n{9*o2b%{2o#9(=b>T&!(}CDY-!^xu8WvCVuVu^S@x`!j zd82-%5lG82t$hW-{@XnvK{<=`2 b6T`wm&v2Mw%zn(_9J4@LC>e!rn7}wunS(*0&9SEu!)kihMEv7BKJDkWii_jnr z+sQ3KY+GOD-V78;@0Cl3?4b_hKK-%oMh#J^uo^-G;^G)M{T>VQuq%8#cS^lXi4-cu zq?Zug?UtVsr-EgR3Q|DfGv~<*w~Da< hKJlGgXfwRRz~;=keHZzPU_18#fg$7tR_5Y~7W6b3b2N>{a% z9S^zbSw~L|$PzPGkjm^2VX4HIaEK)WirAa~azF6h=6E)Sg%5! M5OMv->7|FO@aq8k@3p_I4@|$s+kxrYq kf6D^GOnlJoF9iNRf>MNp1z3BiT(y-@3pw4+W}(TMsQ!;!;fL}nz_ z^rR<%&V=y@uetD4&6LOv)h^Z=>Ukr*m3TrBn#{eYbDxVvmtCkXB6muv+Yc=wn>dht zp~JT~WWagDbTOTXwvw&i3KmYwunzQC1R ypq>z_S8$p}(it8qV*NM1t9#2L*4;*>AScoeV^f9{uh!m-dw2n9o| zt`%wvxQCiB#$Hs5Z0KEI4=zPVojjl4PUjNRvk=QQ1)Oxf?l3=d<)cA-r(0a}bG7Id ztAzC3zY-W9n<2$zsh0~(04#(N&xowWD{Ia{EGHh2&T;AogpU_FHUC)<;z>6z38bp< zuJA_5Om}OtJ{gfJHK3A(r7@JIfwTNAzj~nTrfO)LmSN49U>lsEifL{0P{1NGE?!C& zU_qwT2+x@GuH_f@8>Ly hGJS(a5d&RN+h;AyG&`T6kxwu1m0SH2WU=>IY=_B(`x zamsjE^l5LLjYZkDtZ|d31 m8Mna;;L3$KpEM&_7X?Tn$pHj(K0@1VMj^aXAZ@*r3iwg;Xn5pfj(3kLUUa#e9X8+}^K>}cL0tnh=lh)22o7E%4+wJqC zQ>Joo?h&A;7=%VK7!Yu|oT0?y@CWHy)`>6Z9Ys5JsvI5CpvOdX*1OCLi@7BFKpJR+ zL!u6Qp~b*K_?^JC-H2mTbX#me>k*6t=t!y7jQG@=O S|JJEtK!J=wOo2*Yu5ux2WZOCjt{47>a*jG_XXu|q z%9f ;Gfa7R*M?)8lccl0d`Yl0sKe0v7s6lP ziRD*RKuf353#zE3G#an#YN_dfJTQHud;0DONfd?l=o(++t|5qmzglsF#UR p^0!b93 ($%FD>IjE=&ROs<8a>moOb z`2ZJTm~E4)wjZ8>o-!zh#7mRY4@Z)nl(x}lOX?>`ZXX#DqcfO5=&wCIWRidWf%M C8Xsyz9y#&2aiDp0ffBuAxUkp^k!h-~qIUfuQSn_7x-Y5w7tNUp#3 zh=B@nUVL%A)1Qb^A3@vO#D2d1M$-;tIc^?r*IjEqRnf40AsH)}F?|#JE=6V|O-)17 zpGYRHpr}Z!^WDZvT$)A8g;4td-QgdQyFDx wS6Ec6s{&et$(@h&mDc}32!I1pJU3Na(T++;nx1dlWMh-|&&jX}=A;TUKSz+! zF6L@unAP>UXmpD+DmNrcW^h^4lJ{diotyU|a<;hY0J$qjJx47I#%4+`(chwYWqGle z38R8un) g0Xkj*l6g1t>j{S0Ix(E!^)P<1%DbvT~ zskNyJ(gex>Dp0kF^UtBw_5PNF{Q2!tnViZkaEau>@I&~P@&30rmu{CWOCqRp`S?a) z?WQMoRIH^|NT^J7CKIZYFDLY lp0-SFI(+ zFsC5!dLoLOA`%>*O&gByXp-tVfQQB_8RTjtgicJVa5U2(F6o|x$HIs<4h5Iidt2*t zqf)?Y-sd>bi4C8w2E*lp%eowJwD}=6<8lNypwXFEPz8Fd+FkD_vjm@+d>WgwET`@) z)ZqO|)C%h9MRj;T*4@t=cPQL2Eg8*uam1Y-75s>3C?^&yX&dzCQeqjJNDtn;L54Cb z<61IhQOWBY8q88aV#Q{cCGek7CD75fLuf9ASjgoHhUbneQbY|;?oHdf 3IYn+Swc1UPek~t5l(}; zu1cY0y!P<$;MDbbP|0*47PsP260rj3$wKqGQ)+$vZ2=fOCIzdXmrNy^n`g8ivuSnN z1Z_Ri(Ods`+A??v<~zm{d8&AeUf}z)$PT)vSGW7Kz#^Q)EOp-H%82Szp|`>CI*eYQ z&~$?t-UvC)N>X24?pa3pawrA|nSdbf{@(ekOryOjCebYoTBP_NUw149=FVgNopt-# z%x_qlCQdrmb%nDg?uFJ=9!gwHh%EK8s%QISkC9ZF$+r4WoEy4#?0G=TZY(k{V{c** zyWS^^+2kaP##I!nRT|;z)8~K eVjlS%vT<4esm1wXMS7Is10o z0x_Mzs6G~CV;T7nJA0@;VIKuGcd)fDZi)bxVx`xN_ZCEVh+ue-L}py;WU`#A5#MXM zb^vk>W`&{7$Hl~g&T~2|_B;1ufuxP##|N$P6=%rlubbn=SFQmq90P>o0H~a^5X71^ z_TK6qLY44T!(xPSX~WHLlym>!Y2W|>Zjkz4Jxo>n*AL5j(xtL(7b(Ke>08?|db&{- zCxQwoM+VEAK^z*IQOv@qjgmo(gmwIdxmmH};$=jm?jZcMD}f|vn#Q_L@HLn`s8{!% zi14Jee=Mg6(>fMgDt|N#zc?l@yb7Uhz$S@d)mhf^b9bhtPO(?%A?D ZlJYSE65kmnB0f%z@1^d5|fhCpw$JT>~jA(B9BKJmQvjf ygvu> zt`FF`fADoc3N?CG#IR)Zg*HGg?Nvzyb+NBU;e^%!<(HrV(|Ma$n(GS?+Bgvt4JZeF z7hM6AE5!ZL;|YY+ 3CrLP8>$!yoFcSRmRC?Jd~#TERb zwzoaCsIH+Ik(g -@s4u9P1A#%dC?9)JLMsug9O1U|YrNLg%AVQLYD>ed6X+MgBG zVMwwti+y`-aV10gBShvrhtmmSt;MdtR5pvzpbu<$UWvKL`M!DYhcR;5Kc2D&7R=o- z&^HyGB`sG7mC6=;X+Bs|g0!#ieVd}mB9o14>K3H2;8+&)k+pHMKGnJFbiy+oCCt#U z+c>zwbEkQY!e|m_En(Rn+)(HN3Tgpahxf!cgVFR5E?T1KLaLaC@PfrMhr^ 8a)m2yFeCw7vDH9v#9ij`yJ{kW0S2J@QAz>1Y l;LW|m}bj@ zP;fO4$aVil4+_!kukzzW6;ju^L!@#i?Dg;2k9A9X3Jz2VN2x{Y1H9-i-)LnOrj^Gb zY2Y6v12`$^@jFBHVv&CuM`y7?7&vzanlAk@$f9NhF({579^b8kp`+TIWp;z5g02tD zCK#055VgwdUCNY0`?ftV&!3W-BL$FV*oy_;rW+heF)>4C@P#K9^UlM?BYN_r;Fa z4yqWvk}xsCWnOaxmrLpI>IgS-m)f8N1%s j~KJ<3{(_xsOm-*4c9x2u;C(ZFro8;G&Pol!-;}{=f^^< zmd<}Yqmjh$tN&;J0FL FNyClMINp?ul-pfmT5B^|9pQ6R44+u&~QV;wxTC@M9kN0u+q+KlA zfy^#9B0^fN02V4n7*8G--6&*TURteGP t~`Rp zVu9Y7li!Glk%NS|<{x&sM?PA>#)b|7mxGXs>yA(dt9Ab7EAt%2{Y}%db|;~00d)oP z%~7+7rQ|Rwelv*a=1}7P30hq9iX%M=VW$$FRiZ6-AZVL%AVI+)k-07nJSDQqsvHCv z?^v8PhQ&!#CsGH!?y8e4^8p@S-yrlEhu5=FJDcO5vY&uFd|(1i=$P{us^!h#>kdO> zGaCvWP(Ms7I81hqKrj$Lz)Y|NYFuVYz_;kA3Wo-TEHNC$0=D_CkC!0z583a5SrSAq z=CE_aabiN_lGa*TVe{e$1?9g2oZ}}s+Ebce%B>t;WXRw$f=G41EfNBP -T#uV9D=*nXnzqjP(KF2n#6`L*;r>G8swtb9!gbLH~5h$ d=RkxwkqPS_}yJnV(O1vP`buCP4`Xfsr3h`49DW ztwd~p{)g#&qg`!I$6lo-Aa$$4D}YZTYFpwooVxA1F@?2a3z><9{+<8Aj~DR)LXB!s zOgiDLsD@t_%a#*c-VjgA=?1b;g1A}lR%jemU7mmX<`R$vy)5WVjG;v)mtJr|&J>kZ zRizL%LgBDL=q@@RzTuia@Q{6b`r7(_g-SK#{1iokZV9-q7ldLo+#&KU?5Y(PAg!r6 zsQ^#X(0gK~;ssrFHEeSsQsvCg?ZYWC^jNvs;HVUcMQobcUu?am904R@7NuY4TA4R& z_ZF)k*=#c+WK9C{91I$y@i$Nw;>3PZDKRsqAd}BbiyzOMWSC%~B hBV;o2Ti>I2BZgT0oB@p~MRf)V3Jhqke4*?qd z$x|U~jrwn4r;a~X3k9u#sdU-d`h}7Nc<00kx%g0h 2wY2uX-y|MMaKKwiopk85^h0fNoZ`x9ceCZO0hRG2KmGWl}y zy|XDLj0-ph3hqXmUZIcOb5)6L%osST)qOwT+syfzFe)3Ui%p*K%B&oO4UrUu-U=8B z@S|w030V+Unb`G>Ib1YYpZ|C#@~}#ja=SkisQk<~4jmF3J-h!xCvhL|uiOns_SF+d z_QKl#@_~0k{&Z-w=y_t_Sw6VH$Pot?5h( lGb0CTCktMn=aL3}EJ&9`-eFA|dy!_78U zFVyx$@33zM5MgJ2%tG|#HIqI-ElKr-MBI;`P8}9Tt12w`a+%{;&g`N_%qUo9S;-eJ zD2T6>^&tsq{q$}EhPv{?GA$$Ruf68;)@+66mcA6#n{y&$=Shq=&U>m`^)R;^OF4IY z)`o)936(Mamv-0)>$%)$m#lMd-+vmyTVznZ?6L7FVscr)YwfyHhD+~2*GptA5zTU} zaLXsN%Y+{1)By(Nw78qkaG<^dN~PPNwxLNhdrLd#3E+>zjH&eV;Z*HvLT3e6B{f4B z5RQ` GTP#GTy+>M%MLQp^!|DNW+sa z8>YqXfEWmr{5jyolCl&0!2oLpQFgE&bnIqzcx!hBa(Q=bi#J9uTB?}m(KQjC&|*|E zK`c#x2H1#6)3uZwZOpH#oI@qN4^%wAKr(y4;X9+53^p_o@O1@upkMb3aHN#e_4}x{ zh^Q!0fg22p@cnf`%C}F#F*$@yO^I4rUED+2Y94}tWk=kcTsI_;eh*>F{|IX*;rxJm zP`;OAl2L5r>4`PS6Fdu120sKl@-qc)Bj%b5)S!t9x1DF2t3Ex6jJD?HY}`h=e$+b{ z{hIuga1KrSvIE;k;DN_clK{cbrL;dTHUtyt&%~Qp-Y^fwp)(X5Akc!c=H(ob&kUV% z;0>!zR>Lc+8xpBJ<~aVnGyW=#j&-gVTr#;ll8&b A;y>~*7W0H&t9buNSlc=AvtBZ&)Xhe*L{~vo0kfI@JyAY zOj48v;mOF*5#*&JhC?wIYONrNLq}AsbkBT2_CCc1lg_wDiUuV#Qwgx_RJ*Ov0U!2A zAg<(2n>hcS$$cG+beUCH&5WGF-T;%s1!5Bhu4m7#cHlSrXTbdc1|t!Z(CUW;L8H^F z9O$zt*W5@J#rr`sR%!!>l0w9}QGo=$H!FVUk>$O$(jSE3iDPg;M==bz#_O!QH}<0G zX6z|!wIAvU4VVBLGyL~?;ryqjv|^%hX|5%$d}#{V*gH?`94I(#vQv6ZZf3IdBbA>c zwEvQ8z+fYyh%NcbN*_&-wo9C$EyQ?V89K3|1mbd3eN?Z;?o_kv{cYhg)j1Uoz#+l) z*p%cS_r-@+n>R){xky3t{Ls|)QR PbCC$b}vu3v4AoNoKJ?|T` zXk5<3)G-g>ycN|_t4V5vFJIgf5(C64#6z)xw^h5Io1+!dR=vk2V*9j8!HIi(W8(r8 zSQNUw{4~|$*98C$8;55(BZ!z7{m%5d3VkfS=G$kXjKk^4wmAd28^g}uJ7)K&XoVeA zz&<%xYlj&E<;Q$IgB#X!3|CwcOG4Y~vfN#dRRmM<2D**$EmA-l_2>AbzLv+gzW#nB zkgnl` zl9NZ09V0+RK>dRtw()`Eaws>ds(ucAJ)Y#>(ZQ20i4_;LfT?8H_j}@2B&Hq=Z4J1r zv&HwTvQQSH_aHV5BR}hG)EnjEOr_F>C2CP7$ctOn#5Zdd5jZ(i1U#aPMV;_2aB@g2 zeiorN5b?o%n)iv;w*7*we&a&kBR$GhZq; ZQHhOClfoFcw(Cq+upI0lewSwJO5zsg{!Npt7;MX(CYYC z{k+$5x;O&Y7RTr6aiU=WL=$E|85at^D7nh1mHe+%0RzcHZn!S%u5`I_e>XEs4(s$! zh2tJG$2kKNdVvq99%%bI-biOSK^CR9pWy@sv%P`2TC%s3t!*^l_w?Nz>BI*-t1MM@ z!>$=#noKaTmP s_uk)9)&VxJnk~0>s^#ZH6A0`^LxFg3t*$ud{KhI;I6s` z4n-7dm^na5%l{#}UJ&370cV;^4l2{&$agYY{cfGxzZ)zb(A9~&k_H97%}UYJSQb>; zS@X%19?Q-SuPs*vtog=$ZgRg&Hf) c#pR!#UszbN#vFBgtu3ACM~vT)gN(b{kpHhF+ld7{O{Jx!84X3DS)3$7Yi~Ck zzywadjqw%;kMzlQ;S#xvPQ#8p<(gF=ScD(ojx`y`HJbzmK90Bn){YYNyX%t>BCHPR z{2|^(-<)UK@ &eZiVv>98^#?BnNX^kDYBh8yX&*1J(+` zRNDKgFW~p%-D0LYY!aX$E4HY*+-LD9C}2YBsig)lThn};*9E`4RGdcJpNOWgGTN9; zM++2bsHFsc)coicx4^ozlz6H<4+#k?YQkWN(Fs!$p`)xC>@^P)5b9B%2rp=Y8g#GK z2rrU3y4buV^Y(5@EY@ORRZ$lxh!gT-Pg}OCRc&uT;&2J4#w3arRZ;Hn#uo*y=o|;u z!iKyoqeObH1YX< Rejs^gYg hGSZ0`@&XT&)YDOR6?BnOog@ds#f1JG|s0aC2)grMJ855FG{m`5bw zk4Fg*Y`K3YaF%B1@gg0xJr@!#{#HJ$+F>lC74(!LwfjH!OF&4#qOFww8jxi<^UhtU z{tlt%70bpAkBy+=qJ9hc8N+Bg#N%y4uH0K@PP!LpJMGI#UiokNBb;#|t<&*C?oJQ2 zBX^FnM)2=+fjK;l<_mAovxf-zi155P+!Qhn6BLYpbrdW(U`mQySo+qH=Z)bJpDWZV zm4J9(+{hv-O~xI6pwI|s-dsO^Y%c0D39hWcbBS-UELLE9(4nN<%5e`Mc(IutyGPDX z8jBz9DEvx7zY-6A$YQ~sQhatHd9jii8ccDksJIR+?Mue+f#C62xdr)Dwxp6JVp&Tg z=(+2NyWx%+K7&~xjTb^whZ*kT)M%DYaiHmgu5hN9^c#==VzmDzikeB&M5ve=Q`o*8 zkK~P -2ckQy#{_Z|R8*c`BfJ7^tP*cGMB?Efl8IyG$ffm3zdNsJ33HMdGixb)2=S^To_? nmNh++wlVWS73C zyT>}3iJHj&Y2QY~dc} ndUC3Y_P z<+w!>?Us60Lp$KGt~>TnmhYgRPNPw- egjR^is#^2##)z|elZ+1DO z^Zf_K;@1g?DfTb$KR?Y`YpBEf^Q$*@R~SR$aRb+Kx05|Dc`oS5?kZzT_;a|Bo^+Ao zn(q9=d+;QNbAR&3P9zf}j(4i!$nUE>nst4?pr`1t -8fc&hx&hZ3xKhnwe4T2{Q(PAAf)1+ zm~}M6pyRCr2cQ#kj;==@So7U}YSHH);BvGRM~kR+*vV3m?eR}9+p_EAm%xg-t|?Sc zt%vVsvk}tKfd!hR>--zb?f{Div>+|vegM_kQi>_QK0M!{EX=ZpfDbh ifBd zOjplJFI;Xg0e14pL5RJGL&2i6q)_eG;z15I%VECVQu$fIb3mpVpKH#~E`8vWxE>_H z#4gPgTM^VwF~j{rD >lDs$Y9Hkb6aa|+i&{K7@xZgAv{^+22 zqF)?%4`gxJ7vBOA7*fPs7TxgrN7dqXrng3(S4!@PxS;S-g|9hwymdyoEk_N?jx#4z z_3H(?-F-7+V!=m|nGnSf!nyv^gA80OZ1$0i)yfH>$YPo88N6)&m*r{F>@Tb8K}M!4 ztJ^t3+dZ2YJT63G 5{CeY(AnwzL!@q$V(Bz5_i `;Y_UeDOp2~+PC*mDN+$sio_t%Thls8x+N*Zt`#>>g!l ?lMEvNxOQs6MqQ6Yr_HG?BIG9kN8cbq>)U0!a!~MoN1H-Lb zfrY;Z%WQU}WA-EPTEvA|jQ=$G{{|YpbimVIdgG<9_n8YC^(WFSuM4p6&o~?NZIpHr z{X#Tlg$)*?n1Qmt9-fb~?MXxJ)LnPv&V0D2)mcPyzE95%e&FL{*@ui=O%c`?07>0g zEeA>v&H9G@_Wtvpf`2v&i0U~^goKh5*^uRjeG;1MVN4B SRCc~xcT&8wG1^o~e zGFPSL+njlRHF;U$Sj4=w1)^@KlTsL;_Cio8I>_VoTF9j||FHH9Z%}r=b_@!uSXOu$ z5EEQR?5>weNQHu%1<2NWevcvDecMeRRRn37F<(tG@c~v$iboAg!7@>53z{W$obhs@ zt_If^Ue`OnGvF=$@BSMuc5RhQgxvJt E 4v&r8x1Os3ba^|QpFPf?3P4-=>O4sky^z6M*5cCtIA9KrqaXu;QG`99# zrjh0$8HWqW Ar~!A*5jk(do8zKNx|pm*DGp1pYU7zFZ{;BNEYfN9ht3u<_aU{%DaZK){UI%JYR! zZhAdDUdP}6$V#v~1VX`Z9=_>{OE~gT186`lI4v>JKaCgku2sC3uI)E44Hn_JrDe zfG>z`9b<7JJ*+bRA0S`r7K5$Mb4Qlei0Utv#G*zxb9Da*cOO~?HZa1gN{bVTlLU`@ zI3bx`q7^6iy?8FTaiO0h#{O+k#onTc=Em&?1&rn-@b(}0cP%LeiE5B4m6sk*sUM`M zECbk>!9YZPkO=kmn743o!gUjNLkppv9=mT$krJ*@v6ZXBjgjgaS&Et-nEIdB$r^&i zuFA?F#O5FH&L~y&JE86l%_X>KgRNQ5Ef;zrtq4i2rdCfu$@Icflr`uMZ|cfHrNdf_ zCq;>i=D23E-N3`!A0RkAA2?fh0BAc>-k)m0S9rfD$Q=AniisI+G358ACgKgx-SzjH zd`s37JF&oeJfYRg#FHIFDr%Z*Mir#L=lI+bn%QbpN)RQ)fCIT4hB#rJK6 MhIUlX2heji?Dar0MJ2lvTG4KUPtFY&^`L{%fOq7wi zL_|2}fBx=kq?ImH7{2E{X%!KQ0j_&$Y#SOc!Rs-d??%>8@F-#uob3@y_$>-fqtOJ) zY%(Jd2n1Y#f(${^<0;uUO&Pxn@Y{zz8R vA%=m@wq#`B zM?k>{8_1%0C4jY67FemQNhoHbhNF;g#d(+Ai^JL~&nFn!Is8B<=gio(CPpeLL{Fs) ztp7Dg!W*f^DS@Sl#nQn58bVG;Sq~M9#@g&ULJ(~K!D?nU?^Z70{|r&z_!Gg>b3Bi7 zVe1==NJ1b#v8B{{fJ}t-L{%g<%!Y;zQ1pjASJ{p5S_amGizfI1p9-GPBuJX7&z?!~ zYKm1dCkVNWTTHg!QI~`!N&fxeuN3e%X!x;&S`3^<1)Ew~cznYojg<<-oyQsC@!gcK zrR$!{CQ{U-cPwTxLLekTiSyh0047dccuhGOCp`c_tJ^^L(~AwuFr#XKqydY~GAg z)Aq3)rP_^^ =-&amURqDyTC zFBUp0!C~-n(7%s{6D{DkWhPN>uMKj}t}gmesEOvRO_FweJU)DMdxOjL@Q(RE31~Vw z@K#GW#Hf5 !4$#D}l&BWWVkIxev6*2n?14AJMxY-~PIZ@8z+8YsNH~Z_ooVb$<=!;=N=IyME zj_X3M$Icdx<9=#z;exo~+I_>xCJp{v3OTKUHM>6buGlA{P!i(G7xV^O4ZDxC+II`3 z-*=k>oyBMmp|(H*LWLqRv)j$t=A6P$8(8c(wel$;cM+K}>HzxIWJ-uXKVNBX%vu?5 zH)t-N qN9){^(HMNW=?yZ@&l;37ZlMW-^t!3j&H-5R#$?wNeTM7MqutiDJ*a6+XP< z2J{iXjvUWmElLttQC1K??s_GTH}o*%C*19rk&=&43X8i5aSzlyJ$_QWkAwk3@}#nm zcKLy?2p`MbV)8xa8Nx+>;k?yJ5iw)6Jdew}j7Glkj?Bi>%KkH%X6rlz!hq>Pg{Ml7 z%DiEOpos<~aOwohq=)r)?m%~Uw}hnR5MZ}hB7f+EE9i}zN%cPy!9Q&>JxQ;b3cJlp z$Y%RXIrOO@vz+fGv%YSrOQ2aoS+R!4?5Sb&Rp@Jp;EM2L9|AsCNMQ5$j&nm@sZPNx zObaORam){HsNxz??!Pr+YM+W(s%3)pr9frTqfSY5K&L>w2}!lVg>3MA){XTCowDCo zJg6gwZpE<(7W@{0q1>G1wPK8*f0a^0K54%d9j)19yot^N7-=)PWznK)PQfRq1rCk) zdPi^&T~k3v1-^+MwUDKFn}i5SaH`ob{~kFA1clM+;s+*m+HNP_TcH+u_#}j#7?Uk? z(dN5CViO1Nhmxyb{Sc_IYcnY4h|_F2xwyserE=mCn_+aORqQR452av5siVA^`RP2O zWtqW~MPS@5?Ev3ZZ$&0I$jLQ YgiP3qP*f5m_{0{EeEnrvE%u3$>c}dKpGYU{k5W3GC5Tn=4TJ_HL&j@c zs)_RB W%Y{dH*CWNd!eE6bL}{`dPZNV`sBZ&Q819X!@Y1zW(%;+v)v_#!?;G0FsTF z|8mQ_EGP5j%IYX5lk-@nu$Q5Vlx)M~iiwml*NfsPh-C%7rSS3DkfZCN{x(BZxeUf} zEOWHTd8)AyxdbryS3n&?FU`g)yCNqKsB@F=EQ8lBP_PCPYgV-t8Yr}P1Ut}Tzp%Re z+>$~rr}v0%L-)k(Zgd(;#Q^4 w#+P0lS6oiKpw%X|{2nt~t>&xBeMdW@bzvFfuRA+1U{0N|=C`NSQ0*N9qZc!%|ld z9?t3JR m35cw&9G5wrn;~5 2&Eg9)zJAA1snW67Y#95ga+pBZ;@fV5x9uH}$obY*{6`?V5triO&+3IOV z_7`+zrPeH3GAy?{c+}q6O&leL^}gc?7lWU(Pn;>QT9yNE%PCp4-Apk_i&^ticA~CM z)I3 )26aKf9!Y`%E?bnk(p8Rd@%qZCjgRX z7!UjukFE|hs Gw8yM?HF*a0(`tH`k m`$ryFZB%C2d3oXilTy$lVVjMEj% ziL=M8CnZd-$@HnD06bJ`t6 NIP-j;K*e423F_ zhqn>vABJr#`=X8ND7>(cGK!5E8G`$70c)F~tv91dp3m+x=3O{hm7af>$y25a1*hcZS?8Ybz zJ$xwL0NTfc53U(~DWmQK=jl3$d&KGZF~02A2Cyn6(Z;UM^+v8`i2wcwA3fx#K$R@= z0QaQg8-o8cNY+UgCBrdKfr%MqxFb1-Zn$a_Sl_)63+)@r=gr~*hs((ipZjJmo)d1y zf&HbX@8b^uB1AOpioExyboA;vGW0CCkZ^M(wA4s;CHK_0?=_~>l_?f0K&+`BoDj#A zXUg!b7I&jQz>G~rPxT0rSii?5 Z-9h7=}h>4%L- zqDMxf+SAhWCb>;eEOHxDUz3Q46>(xxfSFKuVCFu(aDZarH9tuzqpAP5HD$cO!;z}j z)wrT-9mY6%exIU?c{h?Cmb?)VY6D|olINrd2VNfpku)`>JJr^^Mar#TABT0bb5!KO zs{htR&K>mL&^Ey_c4J@R74gxT!MM2}`6ofJrg^OS5)EDn;%=NP+zf`_-;6IHD&b*^ z0S>&tqzk?XGci&>O*fNa*n#yv;Cr{>YlWb#52E{rQN;)ZvMM07e(MybBveI A^B(}8-Q)T ;dzn<=w()^mm5+|zkWojVn|ppFt9c87T=hDk&<_6 zIiiB^L*=7`m=HNAf>1~d8*&7v1zfn7%|Y*da*a@U7xWWoh^3gi>e7qBarQ4oCQ)%` z-Y!dL>0wcw6GhhJb${mF(cCAO?ANK88dyg>2zc2ljq0W%q=;BAV21W<-L C6JBY6ZuvpM~sygvD4hC7ME<9 z7E7RL-FEz1O;y8D%|N;9f|QaK*EwXXXz^NjR?WG_7d-A2WIrxWxC@sl4x~+8rx}i( z$B`^
1f< z72i&F<4b&sTXK^D!G%gQdG0v%zY5~DV2?pED{voIyv;y$xNan=aW{LHM3WF}tZ{v6 zM?W!mKcle0Q|e9TuvmIidArazc$|C=cBF!@NH)L<#;7yp@A7c#a)rq+Ar#6DCBY zAR2Uq{!J3;hJ-^Tw};ETPH!k4h&w&j7YH|BNbvH3$@6qXh?C6?|Ct_!XUJvU@RFH6 zo1qrA=sD5Aq^)0`nEF6y^>-s4VtXZgtrWiv%ao{?;CNKbZdzyqaH%?qfP-+W!V~+J zuM@t1*}Rj&PGD;8b(5c;1HI-FG_w%K`?MW3^7v5jCmkNwCcD9J99YL^?uElziJXq{ zWyg*?gxk&^5n?Tu;nFH#pT8%@^L{x25&!k07GI^#6Y;5 +Ui~_iG^?Mq}uxq>B2BG6qFQ$W(~9;<3Eo z%wyWVML?}ceHjo0wwA&+dU(*49>)hCG=Z;$jjf1uK8Iq$PG6+#Jw}5s4p$jh8zs>V z(~8nb0hOZu6na;)9ZuW>d9lU3%oItp+jt&+P4K53i|boLJ~eO{O6p{56+4_Wxz^mq zdQ_J0y5r7wD~loDR57wWOk SJP}2w}#}!ajXD ;dc+xsK^st=PCtJ;h zZ9FHh{2QC;{^at4BM3v!3#9Xr%(yVV{KN^>+ZuMyvZJX91P%@k$(8xOOUht2Q9=E` zEd<~r&bnjd`R|IltSbws=j-%;V@bN=Xn67Kl%bWT2ds;Y-#Fjl)ur~}_)AKH-=9(r zSwzUX5I{tHE^I}#^fQ%Z!->_YxVknTFRpvP(9C$?9sRI7dT9agBix3Y2as7B8ey_t zj;`zV&g}3qV7$t^;!QOwE`|1h_SXQD=E2oDsQhOyjG^ijPwC);V-sC7Na6R?s-qQX zeOXDN@Eb8mk`E4zVtYv#Kqf)iZ#H7fFRwfNZTucf9;x6H?bU{HxH~)*b^b=9kG~LN z@W^WLG>7AYt{qNN4Lw Q(7*zAiq;LIndBDG51$JrH?$4$#qNs_n2KOPirC z1Rz6b%lz<_bv-G5vep(tFV%6O8`rj~H@gJCr_~L(t@da}SuyB^ap!k8_Yw54RBCDH zK(1Fds7LEZ*`b#+qm@y;?-bxibAqMr-Hd6etnqMMF!V^mAhC-%r9>w`mct?5CD}eL`FqHcRjyiH|KkUBqR;=GeL=B7J9l^3d9?}tLCh6n&(f@ zJ>ac$lmTPiV{5Ca#c6QXq5 J~Rcqgc^z^!t7j~&eOP`Tq{vvo!Ivh?VTv7%SCqSt-C=$W9t zW9DZh{J^&=IRdsOM4^RWm$_{ivgZ56yY)kT;Lpj{2ee+LYw=KOgpxNhbn*l9Gmyu| z<;uMb*p}YA?uPFYF)A6nw$abb@;?knKZ737G!_s@Q&Wk{Ou)@J;tN|pIs6*hp|^7x zfOK8qZ0y<0rhW>mRpK3Snio}90=Zs(a1f0|2hp+I&i*hzTFOY)kI0F}Abk3ZXJL~c z*7DAgp`BfC>5B(X|3cpFw)74_2=zsLce;h+$ED^`UZ$bX%m=&_b~W+E+UE_?vVQv{ z%(r``RrzvWeqHYlE5W$y;!2?X3BDE5XwCk;ugB+=cU4>0ca^R$#_!$%l9{X^K`}kN zFVQ$lw!3*kfiqzp8mlj)Kw%Z5{`7yqNkEj(E>T@ob#J=!O3WS`;zKkM)=92-8# zk4F-PGK?axbmnI4M8^Tj4Gg(H3q9Gb>!Sr3U2Z|WrsLs8*q~%%dqy{u&1Gnih>Z$q z?B>Ryq>M-sHJkHusHBAGNrS@2 AWAXc{^m;@jb*q(|E(Xn!G+az=JQsuY(q~yvmy=>uPhMh|kv^bM5}zZ@ z` hC+TgzsrgwAu3kHB&r$!n~0@9~ZH({MDmE=PngSffCfLafVL(43GE4 zR^DX5eEe=3{t#Trz77u$1;5r|#KRy)xPmh-k{v4vtgqY9vZ>xj=>_&uOxuhYQ>&H# zl&^OZ$WddN+jWBUoY9GzFFCp-@7i;3#bJR(jvhVZ4BV)$6(Q7Vp_E_^2nUD Rldv8j&VfqNf0p5Zp3xj$Q=8!Z7**;Z)jSVWN&p7Cy}GdvN?!v z<03hI8Wtb7#pD}6f@CFWxL=0~CugPq6vQ>;?!?t{49Oy@zF-bIogjm9V8*L6w4DH1 zLYjh|RJLw8LDK>j)DYezoovHR8umRIy e@OhitFZ^F|4-3$Uv{Q>} z^S{_;&%4No5Ta44pHGK#%e56FRIqg*wH=lkL%LV`KaDlC=VtbK$X9Y)Id*Yn9^cj+ z+PVp(rKB-EfjO7`5w k^jlEI{vNxELjm#iRdnO+o0n$`P|7 zY~3=q=l0 (*6d&M^KH4TA4v-*b-(~7!cFN z`60Ayg@(ts*ZVAi(o)T^!4~SwwU!$1nl3234l+N_ l(q?>s zougn9_ApUfN`huB##`REDU&en(R1LFx*~hmzBrIs{Fw;wv&_r z_G7B$-e+F`zyQMjg#M@c5u0_DBZxS7*sLPLKSsMEBmkD6U*on%i#vq0I34uo4B*=P zh^Wh<4Lmgu{Qa@azmJ0Ni;8w)LR`xXW b%`| z#<(B%M7b7RNX=&}73ia)#v68WevMYGfc^ZS)ET@v3Che=AwEJPPr4-^B0L%uvsqP* z|5PQTJl5z6RVx7IIbu;zHV5gzMlja40p?Qo#beiJfN{g;W=a+yJa$UC@{53 V0 zZsB{EelhG1nvFokDB`{6uIwc|N*%n*_PM|75{|B<_^(-TcqKu!6g?VB*Y79t+Pjd< z{qc2?%HvCNEE%lQVcqXv`bW5o6CL*=EA~5<$pUh;7hfdoH%?U&ym9De $^^!g$zK0L?qWNDppdIOrgxZ4K^-}hdT3#ub!I@#%W>W~81nf5 Yk z!1a#m%=73n{qG0x_-~rQ{5|*ZMg!6G_|Qc5BU1ydbs7_!=8JSc(ZS8o5 zQk1 iZ2 `-H26-*l)} zmRwN$FnNN!o%9Ej+Sp1G{9>vDGLdIb_(0fI^}^Ug4AT?yTt3qtu%`{WRr1PW59tNN zxiJv_`oZyE9;c)L*z!jG@S65J5~?q4G!&Qh;d{#1XUQz<_8=^|85Y)N#xiNN z^F(mZG-I|~=>}kQa3`Db`gFnMcVkw*on8$Eo7xdQOFsBd9Vm)8=s5z?q@^9gqy8u2 z&wFLHz2DjCzJo(!YnenL6gt8&9jgBrVkA(XOID6GTVdcDQqwX;dx5p7V*ZayI3Bb8 z;wGk7>yu)XmLMOKmD9zV-*~xl{K$9Fi8brUrbzr)+b#GAM#>92_4kE6hoCjpcqJ7| zS&Xj9oYT{yr}vr}>RGWOT7uX_KVf&!Lb9bMd3iV&&M(5VjWd^L1j9O18#F^l&$;7@ zTZAS@rAXn<3_>t8g0}qbe=qH~(O@Y BHy?!(gJPIDHk-eG7uvcgeL!zWe)Q9aye{sdsMA3%e#)NOLH2QQq D zcTCRa&`3Yx<&ORMX1oK#rEr>F(SpEkUOh7YY}R}~*;{-?ijBl_Cm{e1@7yR~!r-8M zCRZI%T;n+7k^|{dMA!o4ocy!{>`v4*4SG;wH|wTw)`7m`w?_8Wdf@J$XoQn@w@Ss} z(2?loFhrN38B&|Hs?lTySKEGs{cY3KTu4t=tNh2W=d3{vmY^9bt=01ZzP6@GB2fm^ zL`^AYvEE}gKw+_Q@Mz5?C4fAi9i~Y&8pj=ngH&8NbXg{(z_~clURF&?W4X(Nhv3{d z>|^DFrV #f9WB$)pEHN7F-*F?;gU&NAhZ3 `JN1L;+k8i%!b|Xi9|sK3$7ogiJwWB9NrXDSPo4| 0w6m3#b;Cu0;9aOu=5 zWzK;Y2L~={O&Blc(b<*UIa^*D>-6vj&$H@5>;XLWTGjNKU+@rzkPmvkC7k33P^_un zU?#$(|FRWn|AX{*1vYK}CRYRFRaaSYl^vu`xgf*tY6#5Hejleox*mJ;S?m_c?nZt% zt($7)baDWy2P_s9Q>o@M!?biBKb{bZ9Ul^PKOtGXkgL=nodPJk9fW}azbuY0@~H%+Yx`e{iABml!1}0 h *g+6XrZ{2Y7t*&A60=&%k9~m~*eP-5Wo<%mpT;z|_B-U6B@v z# 4{X>CY1 zu2op3=h$asmIFgKMqe!ZOyBGsEzSych-=QWrMUEg=Y4!=U-GmjXV2E+ac~Z%(k8d5 zuTV49)iFH~J$X&ae5X4ce{kG+I ;3DJ;{#SPAc(P>=EghQ9$ z$%WjrpqInG|L}pxLZrgb#g*LmdrpEBEGlffG!aKn8v=Fu!Fn$rkFA$#hABk+06l exRHGCJb6qj8vUjE+{SI!djKG0i(B1ewm6Hvxq$VG({Sh1+R z(^A)>H}u{(yZ4=oVs16uTAsqg-VPjZV%yJRjm!mnY5^aSm4lP4+`6|k50QW*(UWkmKQxl-iae!+Cng_lJNkF%`3>vU zk3r{9sBWc=Rn}agAPi-u&ln0Rf&r3M?;B2UiuuXQj2SMI)Ve9n;m@$rxL99NjuXi; zj72jp6s|bbBghPGN8Ra#mD~?-cM|lybUYsXUo@gPjIoE7+p78^w#pEBK6{c8cB8We zPmUJVUP}LJHU_9zeSso}{;sTW{?mrDp)G&ZMa>qu@Y{Vkk}vDQ+
Hk(&9@HddDKrcaK_g`t&vOe&z0C X2QXFA*$}Np*VWww$R<(W_upiEHfp3YSq-jwPhkGU#^YbT z<