arql 0.2.10 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,6 +4,9 @@ module Arql
4
4
  def open(options)
5
5
  ActiveRecord::Base.establish_connection(options)
6
6
  $C = ActiveRecord::Base.connection
7
+ $C.define_singleton_method(:dump) do |filename, no_create_db=false|
8
+ Arql::Mysqldump.new.dump_database(filename, no_create_db)
9
+ end
7
10
  end
8
11
  end
9
12
  end
@@ -1,3 +1,4 @@
1
+ require 'arql/concerns'
1
2
  module Arql
2
3
  module Extension
3
4
  extend ActiveSupport::Concern
@@ -34,6 +35,10 @@ module Arql
34
35
  [self].write_excel(filename, *fields, **options)
35
36
  end
36
37
 
38
+ def dump(filename, batch_size=500)
39
+ [self].dump(filename, batch_size)
40
+ end
41
+
37
42
  included do
38
43
  end
39
44
 
@@ -65,6 +70,10 @@ module Arql
65
70
  def to_create_sql
66
71
  ActiveRecord::Base.connection.exec_query("show create table #{table_name}").rows.last.last
67
72
  end
73
+
74
+ def dump(filename, no_create_table=false)
75
+ Arql::Mysqldump.new.dump_table(filename, table_name, no_create_table)
76
+ end
68
77
  end
69
78
  end
70
79
 
@@ -145,6 +154,7 @@ module Arql
145
154
  @@models = []
146
155
  ActiveRecord::Base.connection.tap do |conn|
147
156
  Object.const_set('ArqlModel', Class.new(ActiveRecord::Base) do
157
+ include ::Arql::Concerns::TableDataDefinition
148
158
  self.abstract_class = true
149
159
 
150
160
  define_singleton_method(:indexes) do
@@ -158,230 +168,6 @@ module Arql
158
168
  }
159
169
  end.t
160
170
  end
161
- # Add a new +type+ column named +column_name+ to +table_name+.
162
- #
163
- # The +type+ parameter is normally one of the migrations native types,
164
- # which is one of the following:
165
- # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
166
- # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
167
- # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
168
- # <tt>:binary</tt>, <tt>:boolean</tt>.
169
- #
170
- # You may use a type not in this list as long as it is supported by your
171
- # database (for example, "polygon" in MySQL), but this will not be database
172
- # agnostic and should usually be avoided.
173
- #
174
- # Available options are (none of these exists by default):
175
- # * <tt>:limit</tt> -
176
- # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
177
- # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
178
- # This option is ignored by some backends.
179
- # * <tt>:default</tt> -
180
- # The column's default value. Use +nil+ for +NULL+.
181
- # * <tt>:null</tt> -
182
- # Allows or disallows +NULL+ values in the column.
183
- # * <tt>:precision</tt> -
184
- # Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
185
- # <tt>:datetime</tt>, and <tt>:time</tt> columns.
186
- # * <tt>:scale</tt> -
187
- # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
188
- # * <tt>:collation</tt> -
189
- # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
190
- # column will have the same collation as the table.
191
- # * <tt>:comment</tt> -
192
- # Specifies the comment for the column. This option is ignored by some backends.
193
- #
194
- # Note: The precision is the total number of significant digits,
195
- # and the scale is the number of digits that can be stored following
196
- # the decimal point. For example, the number 123.45 has a precision of 5
197
- # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
198
- # range from -999.99 to 999.99.
199
- #
200
- # Please be aware of different RDBMS implementations behavior with
201
- # <tt>:decimal</tt> columns:
202
- # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
203
- # <tt>:precision</tt>, and makes no comments about the requirements of
204
- # <tt>:precision</tt>.
205
- # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
206
- # Default is (10,0).
207
- # * PostgreSQL: <tt>:precision</tt> [1..infinity],
208
- # <tt>:scale</tt> [0..infinity]. No default.
209
- # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
210
- # but the maximum supported <tt>:precision</tt> is 16. No default.
211
- # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
212
- # Default is (38,0).
213
- # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
214
- # Default unknown.
215
- # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
216
- # Default (38,0).
217
- #
218
- # == Examples
219
- #
220
- # User.add_column(:picture, :binary, limit: 2.megabytes)
221
- # # ALTER TABLE "users" ADD "picture" blob(2097152)
222
- #
223
- # Article.add_column(:status, :string, limit: 20, default: 'draft', null: false)
224
- # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
225
- #
226
- # Answer.add_column(:bill_gates_money, :decimal, precision: 15, scale: 2)
227
- # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
228
- #
229
- # Measurement.add_column(:sensor_reading, :decimal, precision: 30, scale: 20)
230
- # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
231
- #
232
- # # While :scale defaults to zero on most databases, it
233
- # # probably wouldn't hurt to include it.
234
- # Measurement.add_column(:huge_integer, :decimal, precision: 30)
235
- # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
236
- #
237
- # # Defines a column that stores an array of a type.
238
- # User.add_column(:skills, :text, array: true)
239
- # # ALTER TABLE "users" ADD "skills" text[]
240
- #
241
- # # Defines a column with a database-specific type.
242
- # Shape.add_column(:triangle, 'polygon')
243
- # # ALTER TABLE "shapes" ADD "triangle" polygon
244
- define_singleton_method(:add_column) do |column_name, type, **options|
245
- conn.add_column(table_name, column_name, type, **options)
246
- end
247
-
248
- # Changes the column's definition according to the new options.
249
- # See TableDefinition#column for details of the options you can use.
250
- #
251
- # Supplier.change_column(:name, :string, limit: 80)
252
- # Post.change_column(:description, :text)
253
- #
254
- define_singleton_method(:change_column) do |column_name, type, options = {}|
255
- conn.change_column(table_name, column_name, type, options)
256
- end
257
-
258
- # Removes the column from the table definition.
259
- #
260
- # Supplier.remove_column(:qualification)
261
- #
262
- # The +type+ and +options+ parameters will be ignored if present. It can be helpful
263
- # to provide these in a migration's +change+ method so it can be reverted.
264
- # In that case, +type+ and +options+ will be used by #add_column.
265
- # Indexes on the column are automatically removed.
266
- define_singleton_method(:remove_column) do |column_name, type = nil, **options|
267
- conn.remove_column(table_name, column_name, type, **options)
268
- end
269
-
270
- # Adds a new index to the table. +column_name+ can be a single Symbol, or
271
- # an Array of Symbols.
272
- #
273
- # The index will be named after the table and the column name(s), unless
274
- # you pass <tt>:name</tt> as an option.
275
- #
276
- # ====== Creating a simple index
277
- #
278
- # Supplier.add_index(:name)
279
- #
280
- # generates:
281
- #
282
- # CREATE INDEX suppliers_name_index ON suppliers(name)
283
- #
284
- # ====== Creating a unique index
285
- #
286
- # Account.add_index([:branch_id, :party_id], unique: true)
287
- #
288
- # generates:
289
- #
290
- # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
291
- #
292
- # ====== Creating a named index
293
- #
294
- # Account.add_index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
295
- #
296
- # generates:
297
- #
298
- # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
299
- #
300
- # ====== Creating an index with specific key length
301
- #
302
- # Account.add_index(:name, name: 'by_name', length: 10)
303
- #
304
- # generates:
305
- #
306
- # CREATE INDEX by_name ON accounts(name(10))
307
- #
308
- # ====== Creating an index with specific key lengths for multiple keys
309
- #
310
- # Account.add_index([:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
311
- #
312
- # generates:
313
- #
314
- # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
315
- #
316
- # Note: SQLite doesn't support index length.
317
- #
318
- # ====== Creating an index with a sort order (desc or asc, asc is the default)
319
- #
320
- # Account.add_index([:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
321
- #
322
- # generates:
323
- #
324
- # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
325
- #
326
- # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
327
- #
328
- # ====== Creating a partial index
329
- #
330
- # Account.add_index([:branch_id, :party_id], unique: true, where: "active")
331
- #
332
- # generates:
333
- #
334
- # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
335
- #
336
- # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
337
- #
338
- # ====== Creating an index with a specific method
339
- #
340
- # Developer.add_index(:name, using: 'btree')
341
- #
342
- # generates:
343
- #
344
- # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
345
- # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
346
- #
347
- # Note: only supported by PostgreSQL and MySQL
348
- #
349
- # ====== Creating an index with a specific operator class
350
- #
351
- # Developer.add_index(:name, using: 'gist', opclass: :gist_trgm_ops)
352
- # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
353
- #
354
- # Developer.add_index([:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
355
- # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
356
- #
357
- # Developer.add_index([:name, :city], using: 'gist', opclass: :gist_trgm_ops)
358
- # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
359
- #
360
- # Note: only supported by PostgreSQL
361
- #
362
- # ====== Creating an index with a specific type
363
- #
364
- # Developer.add_index(:name, type: :fulltext)
365
- #
366
- # generates:
367
- #
368
- # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
369
- #
370
- # Note: only supported by MySQL.
371
- #
372
- # ====== Creating an index with a specific algorithm
373
- #
374
- # Developer.add_index(:name, algorithm: :concurrently)
375
- # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
376
- #
377
- # Note: only supported by PostgreSQL.
378
- #
379
- # Concurrently adding an index is not supported in a transaction.
380
- #
381
- # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
382
- define_singleton_method(:add_index) do |column_name, options = {}|
383
- conn.add_index(table_name, column_name, options)
384
- end
385
171
  end)
386
172
  conn.tables.each do |table_name|
387
173
  table_comment = conn.table_comment(table_name)
@@ -459,6 +245,9 @@ module Arql
459
245
  records.write_excel(filename, *fields, **options)
460
246
  end
461
247
 
248
+ def dump(filename, batch_size=500)
249
+ records.dump(filename, batch_size)
250
+ end
462
251
  end
463
252
  end
464
253
  end
@@ -83,11 +83,17 @@ class Array
83
83
  end
84
84
  csv << fields
85
85
  end
86
+ if size > 0 && first.is_a?(Hash)
87
+ if fields.empty?
88
+ fields = first.keys
89
+ end
90
+ csv << fields
91
+ end
86
92
  each do |row|
87
93
  if row.is_a?(Array)
88
94
  csv << row.map(&:to_s)
89
95
  else
90
- csv << row.slice(fields).values.map(&:to_s)
96
+ csv << row.slice(*fields).values.map(&:to_s)
91
97
  end
92
98
  end
93
99
  end
@@ -105,14 +111,29 @@ class Array
105
111
  end
106
112
  sheet.add_row(fields, types: [:string] * fields.size)
107
113
  end
114
+ if size > 0 && first.is_a?(Hash)
115
+ if fields.empty?
116
+ fields = first.keys
117
+ end
118
+ sheet.add_row(fields, types: [:string] * fields.size)
119
+ end
108
120
  each do |row|
109
121
  if row.is_a?(Array)
110
122
  sheet.add_row(row.map(&:to_s), types: [:string] * row.size)
111
123
  else
112
- sheet.add_row(row.slice(fields).values.map(&:to_s), types: [:string] * fields.size)
124
+ sheet.add_row(row.slice(*fields).values.map(&:to_s), types: [:string] * fields.size)
113
125
  end
114
126
  end
115
127
  end
116
128
  end
117
129
  end
130
+
131
+ def dump(filename, batch_size=500)
132
+ File.open(File.expand_path(filename), 'w') do |file|
133
+ group_by(&:class).each do |(klass, records)|
134
+ file.puts(klass.to_upsert_sql(records, batch_size))
135
+ end
136
+ end
137
+ {size: size, file: File.expand_path(filename)}
138
+ end
118
139
  end
@@ -3,19 +3,35 @@ class Hash
3
3
  generate_excel(filename) do |workbook|
4
4
  each do |sheet_name, sheet_data|
5
5
  workbook.add_worksheet(name: sheet_name) do |sheet|
6
- if sheet_data.is_a?(Hash) && sheet_data[:fields].present?
7
- fields = sheet_data[:fields].map(&:to_s)
8
- else
9
- fields = sheet_data[:data].first.attributes.keys
10
- end
6
+ if sheet_data.is_a?(Hash)
7
+ fields = sheet_data[:fields].map(&:to_s)
11
8
  sheet.add_row(fields, types: [:string] * fields.size)
12
- sheet_data = sheet_data[:data]
9
+ sheet_data[:data].each do |row|
10
+ sheet.add_row(row.slice(*fields).values.map(&:to_s), types: [:string] * fields.size)
11
+ end
13
12
  end
14
- sheet_data.each do |row|
15
- if row.is_a?(Array)
16
- sheet.add_row(row.map(&:to_s), types: [:string] * row.size)
17
- else
18
- sheet.add_row(row.slice(fields).values.map(&:to_s), types: [:string] * fields.size)
13
+
14
+ if sheet_data.is_a?(Array)
15
+ if sheet_data.size > 0 && sheet_data.first.is_a?(ActiveModel::Base)
16
+ fields = sheet_data.first.attributes.keys
17
+ sheet.add_row(fields, types: [:string] * fields.size)
18
+ sheet_data.each do |row|
19
+ sheet.add_row(row.slice(*fields).values.map(&:to_s), types: [:string] * fields.size)
20
+ end
21
+ end
22
+
23
+ if sheet_data.size > 0 && sheet_data.first.is_a?(Hash)
24
+ fields = sheet_data.first.keys
25
+ sheet.add_row(fields, types: [:string] * fields.size)
26
+ sheet_data.each do |row|
27
+ sheet.add_row(row.slice(*fields).values.map(&:to_s), types: [:string] * fields.size)
28
+ end
29
+ end
30
+
31
+ if sheet_data.size > 0 && sheet_data.first.is_a?(Array)
32
+ sheet_data.each do |row|
33
+ sheet.add_row(row.map(&:to_s), types: [:string] * fields.size)
34
+ end
19
35
  end
20
36
  end
21
37
  end
@@ -1,6 +1,9 @@
1
+ require 'arql/concerns'
1
2
  module Kernel
2
3
  CSV_BOM = "\xef\xbb\xbf"
3
4
 
5
+ include ::Arql::Concerns::GlobalDataDefinition
6
+
4
7
  def sql(sql)
5
8
  ActiveRecord::Base.connection.exec_query(sql)
6
9
  end
@@ -25,7 +28,7 @@ module Kernel
25
28
  Terminal::Table.new { |t|
26
29
  t.headings = ['PK', 'Name', 'SQL Type', 'Limit', 'Precision', 'Scale', 'Default', 'Nullable', 'Comment']
27
30
  t.rows = table[:columns].map { |column|
28
- pk = if [::ActiveRecord::Base.connection.primary_key(table_name)].flatten.include?(column_name)
31
+ pk = if [::ActiveRecord::Base.connection.primary_key(table_name)].flatten.include?(column)
29
32
  'Y'
30
33
  else
31
34
  ''
@@ -44,7 +47,7 @@ module Kernel
44
47
  Terminal::Table.new { |t|
45
48
  t.headings = ['PK', 'Name', 'SQL Type', 'Limit', 'Precision', 'Scale', 'Default', 'Nullable', 'Comment']
46
49
  t.rows = table[:columns].map { |column|
47
- pk = if [::ActiveRecord::Base.connection.primary_key(table_name)].flatten.include?(column_name)
50
+ pk = if [::ActiveRecord::Base.connection.primary_key(table_name)].flatten.include?(column)
48
51
  'Y'
49
52
  else
50
53
  ''
@@ -92,7 +95,7 @@ module Kernel
92
95
  def generate_excel(filename)
93
96
  Axlsx::Package.new do |package|
94
97
  yield(package.workbook)
95
- package.serialize(filename)
98
+ package.serialize(File.expand_path(filename))
96
99
  end
97
100
  end
98
101
 
@@ -106,159 +109,4 @@ module Kernel
106
109
  end
107
110
  end
108
111
 
109
- # Example:
110
- #
111
- # create_table :post, id: false, primary_key: :id do |t|
112
- # t.column :id, :bigint, precison: 19, comment: 'ID'
113
- # t.column :name, :string, comment: '名称'
114
- # t.column :gmt_created, :datetime, comment: '创建时间'
115
- # t.column :gmt_modified, :datetime, comment: '最后修改时间'
116
- # end
117
- #
118
- # Creates a new table with the name +table_name+. +table_name+ may either
119
- # be a String or a Symbol.
120
- #
121
- # There are two ways to work with #create_table. You can use the block
122
- # form or the regular form, like this:
123
- #
124
- # === Block form
125
- #
126
- # # create_table() passes a TableDefinition object to the block.
127
- # # This form will not only create the table, but also columns for the
128
- # # table.
129
- #
130
- # create_table(:suppliers) do |t|
131
- # t.column :name, :string, limit: 60
132
- # # Other fields here
133
- # end
134
- #
135
- # === Block form, with shorthand
136
- #
137
- # # You can also use the column types as method calls, rather than calling the column method.
138
- # create_table(:suppliers) do |t|
139
- # t.string :name, limit: 60
140
- # # Other fields here
141
- # end
142
- #
143
- # === Regular form
144
- #
145
- # # Creates a table called 'suppliers' with no columns.
146
- # create_table(:suppliers)
147
- # # Add a column to 'suppliers'.
148
- # add_column(:suppliers, :name, :string, {limit: 60})
149
- #
150
- # The +options+ hash can include the following keys:
151
- # [<tt>:id</tt>]
152
- # Whether to automatically add a primary key column. Defaults to true.
153
- # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
154
- #
155
- # A Symbol can be used to specify the type of the generated primary key column.
156
- # [<tt>:primary_key</tt>]
157
- # The name of the primary key, if one is to be added automatically.
158
- # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
159
- #
160
- # If an array is passed, a composite primary key will be created.
161
- #
162
- # Note that Active Record models will automatically detect their
163
- # primary key. This can be avoided by using
164
- # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
165
- # to define the key explicitly.
166
- #
167
- # [<tt>:options</tt>]
168
- # Any extra options you want appended to the table definition.
169
- # [<tt>:temporary</tt>]
170
- # Make a temporary table.
171
- # [<tt>:force</tt>]
172
- # Set to true to drop the table before creating it.
173
- # Set to +:cascade+ to drop dependent objects as well.
174
- # Defaults to false.
175
- # [<tt>:if_not_exists</tt>]
176
- # Set to true to avoid raising an error when the table already exists.
177
- # Defaults to false.
178
- # [<tt>:as</tt>]
179
- # SQL to use to generate the table. When this option is used, the block is
180
- # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
181
- #
182
- # ====== Add a backend specific option to the generated SQL (MySQL)
183
- #
184
- # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4')
185
- #
186
- # generates:
187
- #
188
- # CREATE TABLE suppliers (
189
- # id bigint auto_increment PRIMARY KEY
190
- # ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
191
- #
192
- # ====== Rename the primary key column
193
- #
194
- # create_table(:objects, primary_key: 'guid') do |t|
195
- # t.column :name, :string, limit: 80
196
- # end
197
- #
198
- # generates:
199
- #
200
- # CREATE TABLE objects (
201
- # guid bigint auto_increment PRIMARY KEY,
202
- # name varchar(80)
203
- # )
204
- #
205
- # ====== Change the primary key column type
206
- #
207
- # create_table(:tags, id: :string) do |t|
208
- # t.column :label, :string
209
- # end
210
- #
211
- # generates:
212
- #
213
- # CREATE TABLE tags (
214
- # id varchar PRIMARY KEY,
215
- # label varchar
216
- # )
217
- #
218
- # ====== Create a composite primary key
219
- #
220
- # create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
221
- # t.belongs_to :product
222
- # t.belongs_to :client
223
- # end
224
- #
225
- # generates:
226
- #
227
- # CREATE TABLE order (
228
- # product_id bigint NOT NULL,
229
- # client_id bigint NOT NULL
230
- # );
231
- #
232
- # ALTER TABLE ONLY "orders"
233
- # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
234
- #
235
- # ====== Do not add a primary key column
236
- #
237
- # create_table(:categories_suppliers, id: false) do |t|
238
- # t.column :category_id, :bigint
239
- # t.column :supplier_id, :bigint
240
- # end
241
- #
242
- # generates:
243
- #
244
- # CREATE TABLE categories_suppliers (
245
- # category_id bigint,
246
- # supplier_id bigint
247
- # )
248
- #
249
- # ====== Create a temporary table based on a query
250
- #
251
- # create_table(:long_query, temporary: true,
252
- # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
253
- #
254
- # generates:
255
- #
256
- # CREATE TEMPORARY TABLE long_query AS
257
- # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
258
- #
259
- # See also TableDefinition#column for details on how to create columns.
260
-
261
- def create_table(table_name, **options, &blk)
262
- ActiveRecord::Base.connection.create_table(table_name, **options, &blk)
263
- end
264
112
  end