sequel 3.41.0 → 3.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,3 +1,31 @@
1
+ === 3.42.0 (2012-12-03)
2
+
3
+ * If an exception occurs while committing a transaction, attempt to rollback (jeremyevans)
4
+
5
+ * Support setting default string column sizes on a per-Database basis via default_string_column_size (jeremyevans)
6
+
7
+ * Reset Model.instance_dataset when extending the model's dataset (jeremyevans)
8
+
9
+ * Make the force_encoding plugin work with frozen strings (jeremyevans)
10
+
11
+ * Add Database#do on PostgreSQL for using the DO anonymous code block execution statement (jeremyevans)
12
+
13
+ * Remove Model.dataset_methods (jeremyevans)
14
+
15
+ * Allow subset to be called inside a dataset_module block (jeremyevans)
16
+
17
+ * Make Dataset#avg, #interval, #min, #max, #range, and #sum accept virtual row blocks (jeremyevans)
18
+
19
+ * Make Dataset#count use a subselect when the dataset has an offset without a limit (jeremyevans) (#587)
20
+
21
+ * Dump deferrable status of unique indexes on PostgreSQL (radford) (#583)
22
+
23
+ * Extend deferrable constraint support to all types of constraints, not just foreign keys (radford, jeremyevans) (#583)
24
+
25
+ * Support Database#copy_table and #copy_into on jdbc/postgres (bdon) (#580)
26
+
27
+ * Make Dataset#update not use a limit (TOP) on Microsoft SQL Server 2000 (jeremyevans) (#578)
28
+
1
29
  === 3.41.0 (2012-11-01)
2
30
 
3
31
  * Add bin/sequel usage guide (jeremyevans)
data/README.rdoc CHANGED
@@ -395,10 +395,10 @@ and not raise an exception outside the block, you can raise the
395
395
  Sequel makes it easy to join tables:
396
396
 
397
397
  order_items = DB[:items].join(:order_items, :item_id => :id).
398
- where(:order_items__order_id => 1234)
398
+ where(:order_id => 1234)
399
399
  # SELECT * FROM items INNER JOIN order_items
400
400
  # ON order_items.item_id = items.id
401
- # WHERE order_items.order_id = 1234
401
+ # WHERE order_id = 1234
402
402
 
403
403
  The important thing to note here is that item_id is automatically qualified with
404
404
  the table being joined, and id is automatically qualified with the last table
@@ -437,9 +437,10 @@ Ruby strings are generally treated as SQL strings:
437
437
  items.where(:x => 'x')
438
438
  # SELECT * FROM items WHERE (x = 'x')
439
439
 
440
- === Qualifying column names
440
+ === Qualifying identifiers (column/table names)
441
441
 
442
- Column references can be qualified by using the double underscore special notation <tt>:table__column</tt>:
442
+ An identifier in SQL is a name that represents a column, table, or schema.
443
+ Identifiers can be qualified by using the double underscore special notation <tt>:table__column</tt>:
443
444
 
444
445
  items.literal(:items__price)
445
446
  # items.price
@@ -449,9 +450,15 @@ Another way to qualify columns is to use the <tt>Sequel.qualify</tt> method:
449
450
  items.literal(Sequel.qualify(:items, :price))
450
451
  # items.price
451
452
 
452
- === Column aliases
453
+ While it is more common to qualify column identifiers with table identifiers, you can also qualify table identifiers with schema identifiers
454
+ to select from a qualified table:
455
+
456
+ posts = DB[:some_schema__posts]
457
+ # SELECT * FROM some_schema.posts
458
+
459
+ === Identifier aliases
453
460
 
454
- You can also alias columns by using the triple undersecore special notation <tt>:column___alias</tt> or <tt>:table__column___alias</tt>:
461
+ You can also alias identifiers by using the triple undersecore special notation <tt>:column___alias</tt> or <tt>:table__column___alias</tt>:
455
462
 
456
463
  items.literal(:price___p)
457
464
  # price AS p
@@ -463,6 +470,11 @@ Another way to alias columns is to use the <tt>Sequel.as</tt> method:
463
470
  items.literal(Sequel.as(:price, :p))
464
471
  # price AS p
465
472
 
473
+ You can use the <tt>Sequel.as</tt> method to alias arbitrary expressions, not just identifiers:
474
+
475
+ items.literal(Sequel.as(DB[:posts].select{max(id)}, :p))
476
+ # (SELECT max(id) FROM posts) AS p
477
+
466
478
  == Sequel Models
467
479
 
468
480
  A model class wraps a dataset, and an instance of that class wraps a single record in the dataset.
data/Rakefile CHANGED
@@ -32,6 +32,18 @@ task :release=>[:package] do
32
32
  sh %{gem push ./#{NAME}-#{VERS.call}.gem}
33
33
  end
34
34
 
35
+ ### Website
36
+
37
+ desc "Make local version of website"
38
+ task :website do
39
+ sh %{#{FileUtils::RUBY} www/make_www.rb}
40
+ end
41
+
42
+ desc "Update Non-RDoc section of sequel.rubyforge.org"
43
+ task :website_rf_base=>[:website] do
44
+ sh %{rsync -rt www/public/*.html rubyforge.org:/var/www/gforge-projects/sequel/}
45
+ end
46
+
35
47
  ### RDoc
36
48
 
37
49
  RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'Sequel: The Database Toolkit for Ruby']
@@ -41,54 +53,47 @@ rdoc_task_class = begin
41
53
  RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
42
54
  RDoc::Task
43
55
  rescue LoadError
44
- require "rake/rdoctask"
45
- Rake::RDocTask
56
+ begin
57
+ require "rake/rdoctask"
58
+ Rake::RDocTask
59
+ rescue LoadError
60
+ end
46
61
  end
47
62
 
48
- RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
63
+ if rdoc_task_class
64
+ RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
49
65
 
50
- rdoc_task_class.new do |rdoc|
51
- rdoc.rdoc_dir = "rdoc"
52
- rdoc.options += RDOC_OPTS
53
- rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
54
- end
66
+ rdoc_task_class.new do |rdoc|
67
+ rdoc.rdoc_dir = "rdoc"
68
+ rdoc.options += RDOC_OPTS
69
+ rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb doc/*.rdoc doc/release_notes/*.txt"
70
+ end if rdoc_task_class
55
71
 
56
- ### Website
72
+ desc "Make rdoc for website"
73
+ task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
57
74
 
58
- desc "Make local version of website"
59
- task :website do
60
- sh %{#{FileUtils::RUBY} www/make_www.rb}
61
- end
62
-
63
- desc "Make rdoc for website"
64
- task :website_rdoc=>[:website_rdoc_main, :website_rdoc_adapters, :website_rdoc_plugins]
65
-
66
- rdoc_task_class.new(:website_rdoc_main) do |rdoc|
67
- rdoc.rdoc_dir = "www/public/rdoc"
68
- rdoc.options += RDOC_OPTS
69
- rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb"
70
- end
71
-
72
- rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
73
- rdoc.rdoc_dir = "www/public/rdoc-adapters"
74
- rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
75
- rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
76
- end
75
+ rdoc_task_class.new(:website_rdoc_main) do |rdoc|
76
+ rdoc.rdoc_dir = "www/public/rdoc"
77
+ rdoc.options += RDOC_OPTS
78
+ rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/*.rb lib/sequel/*.rb lib/sequel/{connection_pool,dataset,database,model}/*.rb doc/*.rdoc doc/release_notes/*.txt lib/sequel/extensions/migration.rb"
79
+ end
77
80
 
78
- rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
79
- rdoc.rdoc_dir = "www/public/rdoc-plugins"
80
- rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
81
- rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb"
82
- end
81
+ rdoc_task_class.new(:website_rdoc_adapters) do |rdoc|
82
+ rdoc.rdoc_dir = "www/public/rdoc-adapters"
83
+ rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
84
+ rdoc.rdoc_files.add %w"lib/sequel/adapters/**/*.rb"
85
+ end
83
86
 
84
- desc "Update Non-RDoc section of sequel.rubyforge.org"
85
- task :website_rf_base=>[:website] do
86
- sh %{rsync -rt www/public/*.html rubyforge.org:/var/www/gforge-projects/sequel/}
87
- end
87
+ rdoc_task_class.new(:website_rdoc_plugins) do |rdoc|
88
+ rdoc.rdoc_dir = "www/public/rdoc-plugins"
89
+ rdoc.options += RDOC_DEFAULT_OPTS + %w'--main Sequel'
90
+ rdoc.rdoc_files.add %w"lib/sequel/{extensions,plugins}/**/*.rb"
91
+ end
88
92
 
89
- desc "Update sequel.rubyforge.org"
90
- task :website_rf=>[:website, :website_rdoc] do
91
- sh %{rsync -rvt www/public/* rubyforge.org:/var/www/gforge-projects/sequel/}
93
+ desc "Update sequel.rubyforge.org"
94
+ task :website_rf=>[:website, :website_rdoc] do
95
+ sh %{rsync -rvt www/public/* rubyforge.org:/var/www/gforge-projects/sequel/}
96
+ end
92
97
  end
93
98
 
94
99
  ### Specs
@@ -0,0 +1,74 @@
1
+ = New Features
2
+
3
+ * Dataset#avg, #interval, #min, #max, #range, and #sum now
4
+ accept virtual row blocks, allowing you to more easily get
5
+ aggregate values of expressions based on the table:
6
+
7
+ DB[:table].sum{some_function(column1, column2)} # => 134
8
+ # SELECT sum(some_function(column1, column2)) FROM table
9
+
10
+ * Database#do has been added on PostgreSQL for using the DO
11
+ anonymous code block execution statement.
12
+
13
+ * Model.dataset_module now uses a Module subclass, which allows
14
+ you to call subset inside a dataset_module block, making
15
+ it easier to consolidate dataset method code:
16
+
17
+ class Album < Sequel::Model
18
+ dataset_module do
19
+ subset(:gold){copies_sold > 500000}
20
+ end
21
+ end
22
+
23
+ * Database#copy_table and #copy_into are now supported on
24
+ jdbc/postgres.
25
+
26
+ * Sequel now supports deferred constraints on constraint types other
27
+ than foreign keys. The only databases that appear to implement
28
+ this are Oracle and PostgreSQL.
29
+
30
+ * Sequel now supports INITIALLY IMMEDIATE deferred constraints via
31
+ the :deferrable=>:immediate constraint/column option.
32
+
33
+ * Sequel now supports setting the default size of string columns,
34
+ via the default_string_column_size option or accessor. In some
35
+ cases, Sequel's default string column size of 255 is too large
36
+ (e.g. MySQL with utf8mb4 character set), and this allows you to
37
+ change it.
38
+
39
+ = Other Improvements
40
+
41
+ * Dataset#count and other methods now use a subselect in the case
42
+ where the dataset has an offset but no limit.
43
+
44
+ * If an error occurs while attempting to commit a transaction, Sequel
45
+ now attempts to rollback the transaction. Some databases do this
46
+ automatically, but not all. Among other things, this fixes issues
47
+ with deferred foreign key constraint violations on SQLite.
48
+
49
+ * When extending a model's dataset, the model's instance_dataset is
50
+ reset, insuring that it will also be extended with the module.
51
+
52
+ * When passing an invalid argument to Dataset#filter, the exception
53
+ message now includes the argument.
54
+
55
+ * The force_encoding plugin now works with frozen string values.
56
+
57
+ * Public methods added to a model dataset_module now have model
58
+ class methods created for them even if the method was added outside
59
+ of a dataset_module block.
60
+
61
+ * On PostgreSQL, Database#indexes now includes a :deferrable entry
62
+ for each index hash, which will be true for unique indexes where
63
+ the underlying constraint is deferrable.
64
+
65
+ * On Microsoft SQL Server 2000, Dataset#update no longer includes a
66
+ limit (TOP), allowing it to work correctly.
67
+
68
+ = Backwards Compatibility
69
+
70
+ * Model.dataset_methods has been removed. This was used to store
71
+ blocks for methods created via def_dataset_method and subset.
72
+ The internals have been changed so that a dataset_module is
73
+ always used in these cases, therefore there was no longer a reason
74
+ for this method.
@@ -17,7 +17,62 @@ module Sequel
17
17
  def self.extended(db)
18
18
  db.send(:initialize_postgres_adapter)
19
19
  end
20
+
21
+ # See Sequel::Postgres::Adapter#copy_into
22
+ def copy_into(table, opts={})
23
+ data = opts[:data]
24
+ data = Array(data) if data.is_a?(String)
25
+
26
+ if block_given? && data
27
+ raise Error, "Cannot provide both a :data option and a block to copy_into"
28
+ elsif !block_given? && !data
29
+ raise Error, "Must provide either a :data option or a block to copy_into"
30
+ end
31
+
32
+ transaction(opts) do |conn|
33
+ begin
34
+ copy_manager = org.postgresql.copy.CopyManager.new(conn)
35
+ copier = copy_manager.copy_in(copy_into_sql(table, opts))
36
+ if block_given?
37
+ while buf = yield
38
+ copier.writeToCopy(buf.to_java_bytes, 0, buf.length)
39
+ end
40
+ else
41
+ data.each { |d| copier.writeToCopy(d.to_java_bytes, 0, d.length) }
42
+ end
43
+ rescue Exception => e
44
+ copier.endCopy
45
+ raise
46
+ ensure
47
+ copier.endCopy unless e
48
+ end
49
+ end
50
+ end
20
51
 
52
+ # See Sequel::Postgres::Adapter#copy_table
53
+ def copy_table(table, opts={})
54
+ synchronize(opts[:server]) do |conn|
55
+ copy_manager = org.postgresql.copy.CopyManager.new(conn)
56
+ copier = copy_manager.copy_out(copy_table_sql(table, opts))
57
+ begin
58
+ if block_given?
59
+ while buf = copier.readFromCopy
60
+ yield(String.from_java_bytes(buf))
61
+ end
62
+ nil
63
+ else
64
+ b = ''
65
+ while buf = copier.readFromCopy
66
+ b << String.from_java_bytes(buf)
67
+ end
68
+ b
69
+ end
70
+ ensure
71
+ raise DatabaseDisconnectError, "disconnecting as a partial COPY may leave the connection in an unusable state" if buf
72
+ end
73
+ end
74
+ end
75
+
21
76
  private
22
77
 
23
78
  # Use setNull for nil arguments as the default behavior of setString
@@ -268,24 +268,8 @@ module Sequel
268
268
  # per row. If a block is not provided, a single string is returned with all
269
269
  # of the data.
270
270
  def copy_table(table, opts={})
271
- sql = if table.is_a?(String)
272
- sql = table
273
- else
274
- if opts[:options] || opts[:format]
275
- options = " ("
276
- options << "FORMAT #{opts[:format]}" if opts[:format]
277
- options << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
278
- options << ')'
279
- end
280
- table = if table.is_a?(::Sequel::Dataset)
281
- "(#{table.sql})"
282
- else
283
- literal(table)
284
- end
285
- sql = "COPY #{table} TO STDOUT#{options}"
286
- end
287
- synchronize(opts[:server]) do |conn|
288
- conn.execute(sql)
271
+ synchronize(opts[:server]) do |conn|
272
+ conn.execute(copy_table_sql(table, opts))
289
273
  begin
290
274
  if block_given?
291
275
  while buf = conn.get_copy_data
@@ -323,18 +307,6 @@ module Sequel
323
307
  # If a block is provided and :data option is not, this will yield to the block repeatedly.
324
308
  # The block should return a string, or nil to signal that it is finished.
325
309
  def copy_into(table, opts={})
326
- sql = "COPY #{literal(table)}"
327
- if cols = opts[:columns]
328
- sql << literal(Array(cols))
329
- end
330
- sql << " FROM STDIN"
331
- if opts[:options] || opts[:format]
332
- sql << " ("
333
- sql << "FORMAT #{opts[:format]}" if opts[:format]
334
- sql << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
335
- sql << ')'
336
- end
337
-
338
310
  data = opts[:data]
339
311
  data = Array(data) if data.is_a?(String)
340
312
 
@@ -344,8 +316,8 @@ module Sequel
344
316
  raise Error, "Must provide either a :data option or a block to copy_into"
345
317
  end
346
318
 
347
- synchronize(opts[:server]) do |conn|
348
- conn.execute(sql)
319
+ synchronize(opts[:server]) do |conn|
320
+ conn.execute(copy_into_sql(table, opts))
349
321
  begin
350
322
  if block_given?
351
323
  while buf = yield
@@ -380,6 +380,7 @@ module Sequel
380
380
  INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with insert into columns output values')
381
381
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with select distinct limit columns into from lock join where group having order compounds')
382
382
  UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with update limit table set output from where')
383
+ UPDATE_CLAUSE_METHODS_2000 = Dataset.clause_methods(:update, %w'update table set output from where')
383
384
  NOLOCK = ' WITH (NOLOCK)'.freeze
384
385
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
385
386
  WILDCARD = LiteralString.new('*').freeze
@@ -801,10 +802,15 @@ module Sequel
801
802
  alias delete_output_sql output_sql
802
803
  alias update_output_sql output_sql
803
804
 
804
- # MSSQL supports the OUTPUT clause for UPDATE statements.
805
- # It also allows prepending a WITH clause.
805
+ # MSSQL supports the OUTPUT and TOP clause for UPDATE statements.
806
+ # It also allows prepending a WITH clause. For MSSQL 2000
807
+ # and below, exclude WITH and TOP.
806
808
  def update_clause_methods
807
- UPDATE_CLAUSE_METHODS
809
+ if is_2005_or_later?
810
+ UPDATE_CLAUSE_METHODS
811
+ else
812
+ UPDATE_CLAUSE_METHODS_2000
813
+ end
808
814
  end
809
815
 
810
816
  # Only include the primary table in the main update clause
@@ -49,6 +49,11 @@ module Sequel
49
49
  metadata_dataset.from(:tab).filter(:tname =>m.call(name), :tabtype => 'VIEW').count > 0
50
50
  end
51
51
 
52
+ # Oracle supports deferrable constraints.
53
+ def supports_deferrable_constraints?
54
+ true
55
+ end
56
+
52
57
  private
53
58
 
54
59
  # Handle Oracle specific ALTER TABLE SQL
@@ -224,6 +224,16 @@ module Sequel
224
224
  :postgres
225
225
  end
226
226
 
227
+ # Use PostgreSQL's DO syntax to execute an anonymous code block. The code should
228
+ # be the literal code string to use in the underlying procedural language. Options:
229
+ #
230
+ # :language :: The procedural language the code is written in. The PostgreSQL
231
+ # default is plpgsql. Can be specified as a string or a symbol.
232
+ def do(code, opts={})
233
+ language = opts[:language]
234
+ run "DO #{"LANGUAGE #{literal(language.to_s)} " if language}#{literal(code)}"
235
+ end
236
+
227
237
  # Drops the function from the database. Arguments:
228
238
  # * name : name of the function to drop
229
239
  # * opts : options hash:
@@ -343,16 +353,17 @@ module Sequel
343
353
  join(:pg_index___ind, :indrelid=>:oid, im.call(table)=>:relname).
344
354
  join(:pg_class___indc, :oid=>:indexrelid).
345
355
  join(:pg_attribute___att, :attrelid=>:tab__oid, :attnum=>attnums).
356
+ left_join(:pg_constraint___con, :conname=>:indc__relname).
346
357
  filter(:indc__relkind=>'i', :ind__indisprimary=>false, :indexprs=>nil, :indpred=>nil, :indisvalid=>true).
347
358
  order(:indc__relname, SQL::CaseExpression.new(range.map{|x| [SQL::Subscript.new(:ind__indkey, [x]), x]}, 32, :att__attnum)).
348
- select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column)
359
+ select(:indc__relname___name, :ind__indisunique___unique, :att__attname___column, :con__condeferrable___deferrable)
349
360
 
350
361
  ds.join!(:pg_namespace___nsp, :oid=>:tab__relnamespace, :nspname=>schema.to_s) if schema
351
362
  ds.filter!(:indisready=>true, :indcheckxmin=>false) if server_version >= 80300
352
363
 
353
364
  indexes = {}
354
365
  ds.each do |r|
355
- i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique]}
366
+ i = indexes[m.call(r[:name])] ||= {:columns=>[], :unique=>r[:unique], :deferrable=>r[:deferrable]}
356
367
  i[:columns] << m.call(r[:column])
357
368
  end
358
369
  indexes
@@ -455,6 +466,16 @@ module Sequel
455
466
  server_version >= 90100
456
467
  end
457
468
 
469
+ # PostgreSQL 9.0+ supports some types of deferrable constraints beyond foreign key constraints.
470
+ def supports_deferrable_constraints?
471
+ server_version >= 90000
472
+ end
473
+
474
+ # PostgreSQL supports deferrable foreign key constraints.
475
+ def supports_deferrable_foreign_key_constraints?
476
+ true
477
+ end
478
+
458
479
  # PostgreSQL supports DROP TABLE IF EXISTS
459
480
  def supports_drop_table_if_exists?
460
481
  true
@@ -604,6 +625,42 @@ module Sequel
604
625
  end
605
626
  end
606
627
 
628
+ # SQL for doing fast table insert from stdin.
629
+ def copy_into_sql(table, opts)
630
+ sql = "COPY #{literal(table)}"
631
+ if cols = opts[:columns]
632
+ sql << literal(Array(cols))
633
+ end
634
+ sql << " FROM STDIN"
635
+ if opts[:options] || opts[:format]
636
+ sql << " ("
637
+ sql << "FORMAT #{opts[:format]}" if opts[:format]
638
+ sql << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
639
+ sql << ')'
640
+ end
641
+ sql
642
+ end
643
+
644
+ # SQL for doing fast table output to stdout.
645
+ def copy_table_sql(table, opts)
646
+ if table.is_a?(String)
647
+ return table
648
+ else
649
+ if opts[:options] || opts[:format]
650
+ options = " ("
651
+ options << "FORMAT #{opts[:format]}" if opts[:format]
652
+ options << "#{', ' if opts[:format]}#{opts[:options]}" if opts[:options]
653
+ options << ')'
654
+ end
655
+ table = if table.is_a?(::Sequel::Dataset)
656
+ "(#{table.sql})"
657
+ else
658
+ literal(table)
659
+ end
660
+ return "COPY #{table} TO STDOUT#{options}"
661
+ end
662
+ end
663
+
607
664
  # SQL statement to create database function.
608
665
  def create_function_sql(name, definition, opts={})
609
666
  args = opts[:args]