schema_definitions.rb 21.8 KB
Newer Older
1
require 'date'
2
require 'set'
3 4
require 'bigdecimal'
require 'bigdecimal/util'
5

6 7
module ActiveRecord
  module ConnectionAdapters #:nodoc:
8 9 10
    # Abstract representation of an index definition on a table. Instances of
    # this type are typically created and returned by methods in database
    # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
11
    class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
12 13
    end

P
Pratik Naik 已提交
14 15 16 17
    # Abstract representation of a column definition. Instances of this type
    # are typically created by methods in TableDefinition, and added to the
    # +columns+ attribute of said TableDefinition object, in order to be used
    # for generating a number of table creation or table changing SQL statements.
18
    class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :sql_type) #:nodoc:
19

A
Aaron Patterson 已提交
20
      def primary_key?
21
        primary_key || type.to_sym == :primary_key
A
Aaron Patterson 已提交
22
      end
23 24
    end

25
    class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc:
26 27
    end

28
    class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc:
29 30 31 32 33 34 35 36 37
      def name
        options[:name]
      end

      def column
        options[:column]
      end

      def primary_key
Y
Yves Senn 已提交
38
        options[:primary_key] || default_primary_key
39
      end
40

41 42
      def on_delete
        options[:on_delete]
43
      end
Y
Yves Senn 已提交
44 45 46 47

      def on_update
        options[:on_update]
      end
Y
Yves Senn 已提交
48 49 50 51 52

      def custom_primary_key?
        options[:primary_key] != default_primary_key
      end

53 54
      def defined_for?(options_or_to_table = {})
        if options_or_to_table.is_a?(Hash)
55
          options_or_to_table.all? {|key, value| options[key].to_s == value.to_s }
56 57 58 59 60
        else
          to_table == options_or_to_table.to_s
        end
      end

Y
Yves Senn 已提交
61 62 63 64
      private
      def default_primary_key
        "id"
      end
65 66
    end

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
    class ReferenceDefinition # :nodoc:
      def initialize(
        name,
        polymorphic: false,
        index: false,
        foreign_key: false,
        type: :integer,
        **options
      )
        @name = name
        @polymorphic = polymorphic
        @index = index
        @foreign_key = foreign_key
        @type = type
        @options = options

        if polymorphic && foreign_key
          raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
        end
      end

      def add_to(table)
        columns.each do |column_options|
          table.column(*column_options)
        end

        if index
          table.index(column_names, index_options)
        end

        if foreign_key
          table.foreign_key(foreign_table_name, foreign_key_options)
        end
      end

      protected

      attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options

      private

      def as_options(value, default = {})
        if value.is_a?(Hash)
          value
        else
          default
        end
      end

      def polymorphic_options
        as_options(polymorphic, options)
      end

      def index_options
        as_options(index)
      end

      def foreign_key_options
        as_options(foreign_key)
      end

      def columns
        result = [["#{name}_id", type, options]]
        if polymorphic
          result.unshift(["#{name}_type", :string, polymorphic_options])
        end
        result
      end

      def column_names
        columns.map(&:first)
      end

      def foreign_table_name
        name.to_s.pluralize
      end
    end

P
Pratik Naik 已提交
145 146 147
    # Represents the schema of an SQL table in an abstract way. This class
    # provides methods for manipulating the schema representation.
    #
148 149
    # Inside migration files, the +t+ object in +create_table+
    # is actually of this type:
P
Pratik Naik 已提交
150 151
    #
    #   class SomeMigration < ActiveRecord::Migration
A
Akira Matsuda 已提交
152
    #     def up
P
Pratik Naik 已提交
153 154 155 156
    #       create_table :foo do |t|
    #         puts t.class  # => "ActiveRecord::ConnectionAdapters::TableDefinition"
    #       end
    #     end
P
Pratik Naik 已提交
157
    #
A
Akira Matsuda 已提交
158
    #     def down
P
Pratik Naik 已提交
159 160 161 162 163 164
    #       ...
    #     end
    #   end
    #
    # The table definitions
    # The Columns are stored as a ColumnDefinition in the +columns+ attribute.
165
    class TableDefinition
P
Pratik Naik 已提交
166 167
      # An array of ColumnDefinition objects, representing the column changes
      # that have been defined.
168
      attr_accessor :indexes
169
      attr_reader :name, :temporary, :options, :as, :foreign_keys
170

171
      def initialize(types, name, temporary, options, as = nil)
172
        @columns_hash = {}
173
        @indexes = {}
174
        @foreign_keys = {}
175
        @native = types
176 177
        @temporary = temporary
        @options = options
178
        @as = as
179
        @name = name
180 181
      end

182 183
      def columns; @columns_hash.values; end

184 185
      # Appends a primary key definition to the table definition.
      # Can be called multiple times, but this is probably not a good idea.
A
Aaron Patterson 已提交
186
      def primary_key(name, type = :primary_key, options = {})
187
        column(name, type, options.merge(:primary_key => true))
188
      end
189 190

      # Returns a ColumnDefinition for the column with name +name+.
191
      def [](name)
192
        @columns_hash[name.to_s]
193 194
      end

195
      # Instantiates a new column for the table.
196 197
      # The +type+ parameter is normally one of the migrations native types,
      # which is one of the following:
198
      # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
199
      # <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
200 201
      # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
      # <tt>:binary</tt>, <tt>:boolean</tt>.
202
      #
203 204 205 206
      # You may use a type not in this list as long as it is supported by your
      # database (for example, "polygon" in MySQL), but this will not be database
      # agnostic and should usually be avoided.
      #
207
      # Available options are (none of these exists by default):
208
      # * <tt>:limit</tt> -
209
      #   Requests a maximum column length. This is number of characters for <tt>:string</tt> and
210
      #   <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns.
211
      # * <tt>:default</tt> -
212
      #   The column's default value. Use nil for NULL.
213
      # * <tt>:null</tt> -
214
      #   Allows or disallows +NULL+ values in the column. This option could
215
      #   have been named <tt>:null_allowed</tt>.
216
      # * <tt>:precision</tt> -
217
      #   Specifies the precision for a <tt>:decimal</tt> column.
218
      # * <tt>:scale</tt> -
219
      #   Specifies the scale for a <tt>:decimal</tt> column.
220 221
      # * <tt>:index</tt> -
      #   Create an index for the column. Can be either <tt>true</tt> or an options hash.
222
      #
223 224
      # Note: The precision is the total number of significant digits
      # and the scale is the number of digits that can be stored following
P
Pratik Naik 已提交
225 226 227 228
      # the decimal point. For example, the number 123.45 has a precision of 5
      # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
      # range from -999.99 to 999.99.
      #
229 230 231 232 233
      # Please be aware of different RDBMS implementations behavior with
      # <tt>:decimal</tt> columns:
      # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
      #   <tt>:precision</tt>, and makes no comments about the requirements of
      #   <tt>:precision</tt>.
234
      # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
235
      #   Default is (10,0).
236
      # * PostgreSQL: <tt>:precision</tt> [1..infinity],
237
      #   <tt>:scale</tt> [0..infinity]. No default.
238
      # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
239
      #   Internal storage as strings. No default.
240
      # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
241
      #   but the maximum supported <tt>:precision</tt> is 16. No default.
242
      # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
243
      #   Default is (38,0).
244
      # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
245
      #   Default unknown.
246
      # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
247
      #   Default (38,0).
248 249 250
      #
      # This method returns <tt>self</tt>.
      #
251
      # == Examples
252
      #  # Assuming +td+ is an instance of TableDefinition
253
      #  td.column(:granted, :boolean)
P
Pratik Naik 已提交
254
      #  # granted BOOLEAN
255
      #
A
AvnerCohen 已提交
256
      #  td.column(:picture, :binary, limit: 2.megabytes)
P
Pratik Naik 已提交
257
      #  # => picture BLOB(2097152)
258
      #
A
AvnerCohen 已提交
259
      #  td.column(:sales_stage, :string, limit: 20, default: 'new', null: false)
P
Pratik Naik 已提交
260
      #  # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
261
      #
A
AvnerCohen 已提交
262
      #  td.column(:bill_gates_money, :decimal, precision: 15, scale: 2)
P
Pratik Naik 已提交
263
      #  # => bill_gates_money DECIMAL(15,2)
264
      #
A
AvnerCohen 已提交
265
      #  td.column(:sensor_reading, :decimal, precision: 30, scale: 20)
P
Pratik Naik 已提交
266
      #  # => sensor_reading DECIMAL(30,20)
267 268 269
      #
      #  # While <tt>:scale</tt> defaults to zero on most databases, it
      #  # probably wouldn't hurt to include it.
A
AvnerCohen 已提交
270
      #  td.column(:huge_integer, :decimal, precision: 30)
P
Pratik Naik 已提交
271
      #  # => huge_integer DECIMAL(30)
272
      #
P
Pratik Naik 已提交
273 274 275 276
      #  # Defines a column with a database-specific type.
      #  td.column(:foo, 'polygon')
      #  # => foo polygon
      #
277 278
      # == Short-hand examples
      #
P
Pratik Naik 已提交
279
      # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types.
280 281 282 283 284
      # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
      # in a single statement.
      #
      # What can be written like this with the regular calls to column:
      #
285
      #   create_table :products do |t|
286 287 288 289 290 291 292
      #     t.column :shop_id,     :integer
      #     t.column :creator_id,  :integer
      #     t.column :item_number, :string
      #     t.column :name,        :string, default: "Untitled"
      #     t.column :value,       :string, default: "Untitled"
      #     t.column :created_at,  :datetime
      #     t.column :updated_at,  :datetime
293
      #   end
294
      #   add_index :products, :item_number
295
      #
296
      # can also be written as follows using the short-hand:
297 298 299
      #
      #   create_table :products do |t|
      #     t.integer :shop_id, :creator_id
300
      #     t.string  :item_number, index: true
A
AvnerCohen 已提交
301
      #     t.string  :name, :value, default: "Untitled"
302
      #     t.timestamps null: false
303 304
      #   end
      #
305
      # There's a short-hand method for each of the type values declared at the top. And then there's
306
      # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
307 308
      #
      # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
309
      # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
310 311
      # options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option
      # will also create an index, similar to calling <tt>add_index</tt>. So what can be written like this:
312 313 314 315
      #
      #   create_table :taggings do |t|
      #     t.integer :tag_id, :tagger_id, :taggable_id
      #     t.string  :tagger_type
A
AvnerCohen 已提交
316
      #     t.string  :taggable_type, default: 'Photo'
317
      #   end
A
AvnerCohen 已提交
318
      #   add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id'
319
      #   add_index :taggings, [:tagger_id, :tagger_type]
320 321 322 323
      #
      # Can also be written as follows using references:
      #
      #   create_table :taggings do |t|
A
AvnerCohen 已提交
324 325 326
      #     t.references :tag, index: { name: 'index_taggings_on_tag_id' }
      #     t.references :tagger, polymorphic: true, index: true
      #     t.references :taggable, polymorphic: { default: 'Photo' }
327
      #   end
328
      def column(name, type, options = {})
329 330 331
        name = name.to_s
        type = type.to_sym

332
        if @columns_hash[name] && @columns_hash[name].primary_key?
333 334 335
          raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
        end

336 337
        index_options = options.delete(:index)
        index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
338
        @columns_hash[name] = new_column_definition(name, type, options)
339 340
        self
      end
341

342 343 344 345
      def remove_column(name)
        @columns_hash.delete name.to_s
      end

A
Aaron Patterson 已提交
346
      [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
347 348 349 350 351
        define_method column_type do |*args|
          options = args.extract_options!
          column_names = args
          column_names.each { |name| column(name, column_type, options) }
        end
352
      end
353

354 355
      # Adds index options to the indexes hash, keyed by column name
      # This is primarily used to track indexes that need to be created after the table
356
      #
A
AvnerCohen 已提交
357
      #   index(:account_id, name: 'index_projects_on_account_id')
358 359 360
      def index(column_name, options = {})
        indexes[column_name] = options
      end
361

362 363 364 365
      def foreign_key(table_name, options = {}) # :nodoc:
        foreign_keys[table_name] = options
      end

366
      # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
367 368 369
      # <tt>:updated_at</tt> to the table. See SchemaStatements#add_timestamps
      #
      #   t.timestamps null: false
370
      def timestamps(*args)
371
        options = args.extract_options!
372 373 374

        options[:null] = false if options[:null].nil?

375 376
        column(:created_at, :datetime, options)
        column(:updated_at, :datetime, options)
377 378
      end

R
Robin Dupret 已提交
379 380 381 382 383
      # Adds a reference. Optionally adds a +type+ column, if the
      # +:polymorphic+ option is provided. +references+ and +belongs_to+
      # are acceptable. The reference column will be an +integer+ by default,
      # the +:type+ option can be used to specify a different type. A foreign
      # key will be created if the +:foreign_key+ option is passed.
384 385 386 387
      #
      #  t.references(:user)
      #  t.references(:user, type: "string")
      #  t.belongs_to(:supplier, polymorphic: true)
388
      #
389
      # See SchemaStatements#add_reference
390
      def references(*args, **options)
391
        args.each do |col|
392
          ReferenceDefinition.new(col, **options).add_to(self)
393 394 395 396
        end
      end
      alias :belongs_to :references

397
      def new_column_definition(name, type, options) # :nodoc:
398
        type = aliased_types(type.to_s, type)
399 400 401 402 403
        column = create_column_definition name, type
        limit = options.fetch(:limit) do
          native[type][:limit] if native[type].is_a?(Hash)
        end

404 405 406 407 408 409 410
        column.limit       = limit
        column.precision   = options[:precision]
        column.scale       = options[:scale]
        column.default     = options[:default]
        column.null        = options[:null]
        column.first       = options[:first]
        column.after       = options[:after]
411
        column.auto_increment = options[:auto_increment]
412
        column.primary_key = type == :primary_key || options[:primary_key]
413
        column
414 415
      end

416 417 418 419 420
      private
      def create_column_definition(name, type)
        ColumnDefinition.new name, type
      end

421
      def native
422
        @native
423
      end
S
Sean Griffin 已提交
424

425 426
      def aliased_types(name, fallback)
        'timestamp' == name ? :datetime : fallback
S
Sean Griffin 已提交
427
      end
428
    end
429

430
    class AlterTable # :nodoc:
A
Aaron Patterson 已提交
431
      attr_reader :adds
432 433
      attr_reader :foreign_key_adds
      attr_reader :foreign_key_drops
434 435

      def initialize(td)
A
Aaron Patterson 已提交
436 437
        @td   = td
        @adds = []
438 439
        @foreign_key_adds = []
        @foreign_key_drops = []
440 441 442 443
      end

      def name; @td.name; end

444 445 446 447 448 449 450 451
      def add_foreign_key(to_table, options)
        @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options)
      end

      def drop_foreign_key(name)
        @foreign_key_drops << name
      end

452 453 454
      def add_column(name, type, options)
        name = name.to_s
        type = type.to_sym
A
Aaron Patterson 已提交
455
        @adds << @td.new_column_definition(name, type, options)
456 457 458
      end
    end

459
    # Represents an SQL table in an abstract way for updating a table.
460 461 462 463 464 465 466
    # Also see TableDefinition and SchemaStatements#create_table
    #
    # Available transformations are:
    #
    #   change_table :table do |t|
    #     t.column
    #     t.index
J
Jarek Radosz 已提交
467
    #     t.rename_index
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
    #     t.timestamps
    #     t.change
    #     t.change_default
    #     t.rename
    #     t.references
    #     t.belongs_to
    #     t.string
    #     t.text
    #     t.integer
    #     t.float
    #     t.decimal
    #     t.datetime
    #     t.timestamp
    #     t.time
    #     t.date
    #     t.binary
    #     t.boolean
    #     t.remove
    #     t.remove_references
    #     t.remove_belongs_to
    #     t.remove_index
    #     t.remove_timestamps
    #   end
    #
    class Table
493 494
      attr_reader :name

495
      def initialize(table_name, base)
496
        @name = table_name
497 498 499 500
        @base = base
      end

      # Adds a new column to the named table.
501
      #
502
      #  t.column(:name, :string)
503 504
      #
      # See TableDefinition#column for details of the options you can use.
505
      def column(column_name, type, options = {})
506
        @base.add_column(name, column_name, type, options)
507 508
      end

509 510 511
      # Checks to see if a column exists.
      #
      # See SchemaStatements#column_exists?
512
      def column_exists?(column_name, type = nil, options = {})
513
        @base.column_exists?(name, column_name, type, options)
514 515
      end

516
      # Adds a new index to the table. +column_name+ can be a single Symbol, or
517
      # an Array of Symbols.
518 519
      #
      #  t.index(:name)
A
AvnerCohen 已提交
520 521
      #  t.index([:branch_id, :party_id], unique: true)
      #  t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
522 523
      #
      # See SchemaStatements#add_index for details of the options you can use.
524
      def index(column_name, options = {})
525
        @base.add_index(name, column_name, options)
526 527
      end

528 529 530
      # Checks to see if an index exists.
      #
      # See SchemaStatements#index_exists?
531
      def index_exists?(column_name, options = {})
532
        @base.index_exists?(name, column_name, options)
533 534
      end

J
Jarek Radosz 已提交
535 536 537
      # Renames the given index on the table.
      #
      #  t.rename_index(:user_id, :account_id)
538 539
      #
      # See SchemaStatements#rename_index
J
Jarek Radosz 已提交
540
      def rename_index(index_name, new_index_name)
541
        @base.rename_index(name, index_name, new_index_name)
J
Jarek Radosz 已提交
542 543
      end

544 545 546
      # Adds timestamps (+created_at+ and +updated_at+) columns to the table.
      #
      #  t.timestamps(null: false)
547
      #
548
      # See SchemaStatements#add_timestamps
549
      def timestamps(options = {})
550
        @base.add_timestamps(name, options)
551 552 553
      end

      # Changes the column's definition according to the new options.
554
      #
A
AvnerCohen 已提交
555
      #  t.change(:name, :string, limit: 80)
556
      #  t.change(:description, :text)
557 558
      #
      # See TableDefinition#column for details of the options you can use.
559
      def change(column_name, type, options = {})
560
        @base.change_column(name, column_name, type, options)
561 562
      end

563
      # Sets a new default value for a column.
564
      #
565 566
      #  t.change_default(:qualification, 'new')
      #  t.change_default(:authorized, 1)
567 568
      #
      # See SchemaStatements#change_column_default
569
      def change_default(column_name, default)
570
        @base.change_column_default(name, column_name, default)
571 572 573
      end

      # Removes the column(s) from the table definition.
574
      #
575 576
      #  t.remove(:qualification)
      #  t.remove(:qualification, :experience)
577 578
      #
      # See SchemaStatements#remove_columns
579
      def remove(*column_names)
580
        @base.remove_columns(name, *column_names)
581 582
      end

583
      # Removes the given index from the table.
584
      #
585 586 587 588 589
      #   t.remove_index(:branch_id)
      #   t.remove_index(column: [:branch_id, :party_id])
      #   t.remove_index(name: :by_branch_party)
      #
      # See SchemaStatements#remove_index
590
      def remove_index(options = {})
591
        @base.remove_index(name, options)
592 593
      end

594
      # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
595
      #
596
      #  t.remove_timestamps
597 598
      #
      # See SchemaStatements#remove_timestamps
599 600
      def remove_timestamps(options = {})
        @base.remove_timestamps(name, options)
601 602 603
      end

      # Renames a column.
604
      #
605
      #  t.rename(:description, :name)
606 607
      #
      # See SchemaStatements#rename_column
608
      def rename(column_name, new_column_name)
609
        @base.rename_column(name, column_name, new_column_name)
610 611
      end

612
      # Adds a reference. Optionally adds a +type+ column, if
613
      # <tt>:polymorphic</tt> option is provided.
614
      #
615
      #  t.references(:user)
616
      #  t.references(:user, type: "string")
617
      #  t.belongs_to(:supplier, polymorphic: true)
618
      #  t.belongs_to(:supplier, foreign_key: true)
619
      #
620
      # See SchemaStatements#add_reference
621 622
      def references(*args)
        options = args.extract_options!
623
        args.each do |ref_name|
624
          @base.add_reference(name, ref_name, options)
625 626 627 628
        end
      end
      alias :belongs_to :references

629
      # Removes a reference. Optionally removes a +type+ column.
630
      #
631 632 633
      #  t.remove_references(:user)
      #  t.remove_belongs_to(:supplier, polymorphic: true)
      #
634
      # See SchemaStatements#remove_reference
635 636
      def remove_references(*args)
        options = args.extract_options!
637
        args.each do |ref_name|
638
          @base.remove_reference(name, ref_name, options)
639 640
        end
      end
641
      alias :remove_belongs_to :remove_references
642

643
      # Adds a column or columns of a specified type.
644
      #
645 646
      #  t.string(:goat)
      #  t.string(:goat, :sheep)
647 648
      #
      # See SchemaStatements#add_column
649 650 651
      [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type|
        define_method column_type do |*args|
          options = args.extract_options!
652 653
          args.each do |column_name|
            @base.add_column(name, column_name, column_type, options)
654
          end
655
        end
656 657
      end

658 659 660 661
      def foreign_key(*args) # :nodoc:
        @base.add_foreign_key(name, *args)
      end

A
Anton 已提交
662 663 664
      def foreign_key_exists?(*args) # :nodoc:
        @base.foreign_key_exists?(name, *args)
      end
665

666 667 668 669 670
      private
        def native
          @base.native_database_types
        end
    end
671
  end
672
end