sequel 4.10.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +58 -0
  3. data/doc/association_basics.rdoc +1 -1
  4. data/doc/cheat_sheet.rdoc +0 -1
  5. data/doc/core_extensions.rdoc +2 -2
  6. data/doc/dataset_filtering.rdoc +5 -5
  7. data/doc/model_hooks.rdoc +9 -0
  8. data/doc/object_model.rdoc +7 -13
  9. data/doc/opening_databases.rdoc +3 -1
  10. data/doc/querying.rdoc +8 -8
  11. data/doc/release_notes/4.11.0.txt +147 -0
  12. data/doc/sql.rdoc +11 -7
  13. data/doc/virtual_rows.rdoc +4 -5
  14. data/lib/sequel/adapters/ibmdb.rb +24 -16
  15. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  16. data/lib/sequel/adapters/jdbc/hsqldb.rb +5 -0
  17. data/lib/sequel/adapters/mock.rb +14 -2
  18. data/lib/sequel/adapters/shared/access.rb +6 -9
  19. data/lib/sequel/adapters/shared/cubrid.rb +5 -0
  20. data/lib/sequel/adapters/shared/db2.rb +5 -0
  21. data/lib/sequel/adapters/shared/firebird.rb +5 -0
  22. data/lib/sequel/adapters/shared/mssql.rb +23 -16
  23. data/lib/sequel/adapters/shared/mysql.rb +12 -2
  24. data/lib/sequel/adapters/shared/oracle.rb +31 -15
  25. data/lib/sequel/adapters/shared/postgres.rb +28 -4
  26. data/lib/sequel/adapters/shared/sqlanywhere.rb +5 -0
  27. data/lib/sequel/adapters/shared/sqlite.rb +12 -1
  28. data/lib/sequel/ast_transformer.rb +9 -7
  29. data/lib/sequel/connection_pool.rb +10 -4
  30. data/lib/sequel/database/features.rb +15 -0
  31. data/lib/sequel/database/schema_generator.rb +2 -2
  32. data/lib/sequel/database/schema_methods.rb +21 -3
  33. data/lib/sequel/database/transactions.rb +8 -4
  34. data/lib/sequel/dataset/actions.rb +13 -7
  35. data/lib/sequel/dataset/features.rb +7 -0
  36. data/lib/sequel/dataset/query.rb +28 -11
  37. data/lib/sequel/dataset/sql.rb +90 -14
  38. data/lib/sequel/extensions/constraint_validations.rb +2 -2
  39. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  40. data/lib/sequel/extensions/eval_inspect.rb +12 -6
  41. data/lib/sequel/extensions/pg_array_ops.rb +11 -2
  42. data/lib/sequel/extensions/pg_json.rb +130 -23
  43. data/lib/sequel/extensions/pg_json_ops.rb +196 -28
  44. data/lib/sequel/extensions/to_dot.rb +5 -7
  45. data/lib/sequel/model/associations.rb +0 -50
  46. data/lib/sequel/plugins/class_table_inheritance.rb +49 -21
  47. data/lib/sequel/plugins/many_through_many.rb +10 -11
  48. data/lib/sequel/plugins/serialization.rb +4 -1
  49. data/lib/sequel/plugins/sharding.rb +0 -9
  50. data/lib/sequel/plugins/single_table_inheritance.rb +4 -2
  51. data/lib/sequel/plugins/timestamps.rb +2 -2
  52. data/lib/sequel/sql.rb +166 -44
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/postgres_spec.rb +199 -133
  55. data/spec/core/connection_pool_spec.rb +6 -0
  56. data/spec/core/database_spec.rb +12 -0
  57. data/spec/core/dataset_spec.rb +58 -3
  58. data/spec/core/expression_filters_spec.rb +67 -5
  59. data/spec/core/mock_adapter_spec.rb +8 -4
  60. data/spec/core/schema_spec.rb +7 -0
  61. data/spec/core_extensions_spec.rb +14 -0
  62. data/spec/extensions/class_table_inheritance_spec.rb +23 -3
  63. data/spec/extensions/core_refinements_spec.rb +14 -0
  64. data/spec/extensions/eval_inspect_spec.rb +8 -4
  65. data/spec/extensions/pg_array_ops_spec.rb +6 -0
  66. data/spec/extensions/pg_json_ops_spec.rb +99 -0
  67. data/spec/extensions/pg_json_spec.rb +104 -4
  68. data/spec/extensions/serialization_spec.rb +19 -0
  69. data/spec/extensions/single_table_inheritance_spec.rb +11 -3
  70. data/spec/extensions/timestamps_spec.rb +10 -0
  71. data/spec/extensions/to_dot_spec.rb +8 -4
  72. data/spec/integration/database_test.rb +1 -1
  73. data/spec/integration/dataset_test.rb +9 -0
  74. data/spec/integration/schema_test.rb +27 -0
  75. metadata +4 -2
@@ -95,8 +95,8 @@ local variable access. This is mostly useful in instance_evaled procs:
95
95
 
96
96
  == VirtualRow Methods
97
97
 
98
- VirtualRow is a class that returns SQL::Identifiers, SQL::QualifiedIdentifiers,
99
- SQL::Functions, or SQL::WindowFunctions depending on how it is called.
98
+ VirtualRow is a class that returns SQL::Identifiers, SQL::QualifiedIdentifiers, or
99
+ SQL::Functions depending on how it is called.
100
100
 
101
101
  == SQL::Identifiers - Regular columns
102
102
 
@@ -165,10 +165,9 @@ distinct method on the returned Function:
165
165
  ds.select{count(col1, col2).distinct}
166
166
  # SELECT count(DISTINCT col1, col2)
167
167
 
168
- == SQL::WindowFunctions - SQL window function calls
168
+ == SQL::Functions with windows - SQL window function calls
169
169
 
170
- SQL::WindowFunctions can be thought of as calls to SQL window functions. Not
171
- all databases support them, but they are very helpful for certain types of
170
+ Not all databases support window functions, but they are very helpful for certain types of
172
171
  queries. To use them, you should just call the over method on the Function
173
172
  object returned, with the options for the window:
174
173
 
@@ -43,8 +43,13 @@ module Sequel
43
43
  end
44
44
 
45
45
  # Create the underlying IBM_DB connection.
46
- def initialize(connection_string)
47
- @conn = IBM_DB.connect(connection_string, '', '')
46
+ def initialize(connection_param)
47
+ @conn = if connection_param.class == String
48
+ IBM_DB.connect(connection_param, '', '')
49
+ else # connect using catalog
50
+ IBM_DB.connect(*connection_param)
51
+ end
52
+
48
53
  self.autocommit = true
49
54
  @prepared_statements = {}
50
55
  end
@@ -193,19 +198,22 @@ module Sequel
193
198
  # Create a new connection object for the given server.
194
199
  def connect(server)
195
200
  opts = server_opts(server)
196
-
197
- # use uncataloged connection so that host and port can be supported
198
- connection_string = ( \
199
- 'Driver={IBM DB2 ODBC DRIVER};' \
200
- "Database=#{opts[:database]};" \
201
- "Hostname=#{opts[:host]};" \
202
- "Port=#{opts[:port] || 50000};" \
203
- 'Protocol=TCPIP;' \
204
- "Uid=#{opts[:user]};" \
205
- "Pwd=#{opts[:password]};" \
206
- )
207
201
 
208
- Connection.new(connection_string)
202
+ connection_params = if opts[:host].nil? && opts[:port].nil? && opts[:database]
203
+ # use a cataloged connection
204
+ opts.values_at(:database, :user, :password)
205
+ else
206
+ # use uncataloged connection so that host and port can be supported
207
+ 'Driver={IBM DB2 ODBC DRIVER};' \
208
+ "Database=#{opts[:database]};" \
209
+ "Hostname=#{opts[:host]};" \
210
+ "Port=#{opts[:port] || 50000};" \
211
+ 'Protocol=TCPIP;' \
212
+ "Uid=#{opts[:user]};" \
213
+ "Pwd=#{opts[:password]};" \
214
+ end
215
+
216
+ Connection.new(connection_params)
209
217
  end
210
218
 
211
219
  # Execute the given SQL on the database.
@@ -432,9 +440,9 @@ module Sequel
432
440
  stmt.num_fields.times do |i|
433
441
  k = stmt.field_name i
434
442
  key = output_identifier(k)
435
- type = stmt.field_type(k).downcase.to_sym
443
+ type = stmt.field_type(i).downcase.to_sym
436
444
  # decide if it is a smallint from precision
437
- type = :boolean if type == :int && convert && stmt.field_precision(k) < 8
445
+ type = :boolean if type == :int && convert && stmt.field_precision(i) < 8
438
446
  type = :blob if type == :clob && Sequel::DB2.use_clob_as_blob
439
447
  columns << [key, cps[type]]
440
448
  end
@@ -160,6 +160,11 @@ module Sequel
160
160
  end
161
161
  end
162
162
 
163
+ # H2 does not support derived column lists
164
+ def supports_derived_column_lists?
165
+ false
166
+ end
167
+
163
168
  # H2 requires SQL standard datetimes
164
169
  def requires_sql_standard_datetimes?
165
170
  true
@@ -122,6 +122,11 @@ module Sequel
122
122
  def uses_clob_for_text?
123
123
  true
124
124
  end
125
+
126
+ # HSQLDB supports views with check option.
127
+ def view_with_check_option_support
128
+ :local
129
+ end
125
130
  end
126
131
 
127
132
  # Dataset class for HSQLDB datasets accessed via JDBC.
@@ -34,6 +34,7 @@ module Sequel
34
34
  # mock adapters for specific database types.
35
35
  SHARED_ADAPTERS = {
36
36
  'access'=>'Access',
37
+ 'cubrid'=>'Cubrid',
37
38
  'db2'=>'DB2',
38
39
  'firebird'=>'Firebird',
39
40
  'informix'=>'Informix',
@@ -41,6 +42,7 @@ module Sequel
41
42
  'mysql'=>'MySQL',
42
43
  'oracle'=>'Oracle',
43
44
  'postgres'=>'Postgres',
45
+ 'sqlanywhere'=>'SqlAnywhere',
44
46
  'sqlite'=>'SQLite'
45
47
  }
46
48
 
@@ -49,7 +51,7 @@ module Sequel
49
51
  SHARED_ADAPTER_SETUP = {
50
52
  'postgres' => lambda do |db|
51
53
  db.instance_eval do
52
- @server_version = 90103
54
+ @server_version = 90400
53
55
  initialize_postgres_adapter
54
56
  end
55
57
  db.extend(Module.new do
@@ -67,9 +69,19 @@ module Sequel
67
69
  @primary_key_sequences = {}
68
70
  end
69
71
  end,
72
+ 'mysql' => lambda do |db|
73
+ db.instance_eval do
74
+ @server_version = 50617
75
+ end
76
+ end,
70
77
  'mssql' => lambda do |db|
71
78
  db.instance_eval do
72
- @server_version = 10000000
79
+ @server_version = 11000000
80
+ end
81
+ end,
82
+ 'sqlite' => lambda do |db|
83
+ db.instance_eval do
84
+ @sqlite_version = 30804
73
85
  end
74
86
  end
75
87
  }
@@ -112,6 +112,7 @@ module Sequel
112
112
  TIME_FUNCTION = 'Time()'.freeze
113
113
  CAST_TYPES = {String=>:CStr, Integer=>:CLng, Date=>:CDate, Time=>:CDate, DateTime=>:CDate, Numeric=>:CDec, BigDecimal=>:CDec, File=>:CStr, Float=>:CDbl, TrueClass=>:CBool, FalseClass=>:CBool}
114
114
 
115
+ EMULATED_FUNCTION_MAP = {:char_length=>:len}
115
116
  EXTRACT_MAP = {:year=>"'yyyy'", :month=>"'m'", :day=>"'d'", :hour=>"'h'", :minute=>"'n'", :second=>"'s'"}
116
117
  COMMA = Dataset::COMMA
117
118
  DATEPART_OPEN = "datepart(".freeze
@@ -189,15 +190,6 @@ module Sequel
189
190
  clone(:from=>@opts[:from] + [table])
190
191
  end
191
192
 
192
- def emulated_function_sql_append(sql, f)
193
- case f.f
194
- when :char_length
195
- literal_append(sql, SQL::Function.new(:len, f.args.first))
196
- else
197
- super
198
- end
199
- end
200
-
201
193
  # Access uses [] to escape metacharacters, instead of backslashes.
202
194
  def escape_like(string)
203
195
  string.gsub(/[\\*#?\[]/){|m| "[#{m}]"}
@@ -208,6 +200,11 @@ module Sequel
208
200
  clone(:into => table)
209
201
  end
210
202
 
203
+ # Access does not support derived column lists.
204
+ def supports_derived_column_lists?
205
+ false
206
+ end
207
+
211
208
  # Access doesn't support INTERSECT or EXCEPT
212
209
  def supports_intersect_except?
213
210
  false
@@ -162,6 +162,11 @@ module Sequel
162
162
  def uses_clob_for_text?
163
163
  true
164
164
  end
165
+
166
+ # CUBRID supports views with check option, but not local.
167
+ def view_with_check_option_support
168
+ true
169
+ end
165
170
  end
166
171
 
167
172
  module DatasetMethods
@@ -222,6 +222,11 @@ module Sequel
222
222
  def uses_clob_for_text?
223
223
  true
224
224
  end
225
+
226
+ # DB2 supports views with check option.
227
+ def view_with_check_option_support
228
+ :local
229
+ end
225
230
  end
226
231
 
227
232
  module DatasetMethods
@@ -146,6 +146,11 @@ module Sequel
146
146
  def type_literal_generic_string(column)
147
147
  column[:text] ? :"BLOB SUB_TYPE TEXT" : super
148
148
  end
149
+
150
+ # Firebird supports views with check option, but not local.
151
+ def view_with_check_option_support
152
+ true
153
+ end
149
154
  end
150
155
 
151
156
  module DatasetMethods
@@ -301,7 +301,7 @@ module Sequel
301
301
  def begin_transaction_sql
302
302
  SQL_BEGIN
303
303
  end
304
-
304
+
305
305
  # Handle MSSQL specific default format.
306
306
  def column_schema_normalize_default(default, type)
307
307
  if m = MSSQL_DEFAULT_RE.match(default)
@@ -487,6 +487,11 @@ module Sequel
487
487
  def type_literal_generic_file(column)
488
488
  :'varbinary(max)'
489
489
  end
490
+
491
+ # MSSQL supports views with check option, but not local.
492
+ def view_with_check_option_support
493
+ true
494
+ end
490
495
  end
491
496
 
492
497
  module DatasetMethods
@@ -609,21 +614,6 @@ module Sequel
609
614
  string.gsub(/[\\%_\[\]]/){|m| "\\#{m}"}
610
615
  end
611
616
 
612
- # There is no function on Microsoft SQL Server that does character length
613
- # and respects trailing spaces (datalength respects trailing spaces, but
614
- # counts bytes instead of characters). Use a hack to work around the
615
- # trailing spaces issue.
616
- def emulated_function_sql_append(sql, f)
617
- case f.f
618
- when :char_length
619
- literal_append(sql, SQL::Function.new(:len, Sequel.join([f.args.first, 'x'])) - 1)
620
- when :trim
621
- literal_append(sql, SQL::Function.new(:ltrim, SQL::Function.new(:rtrim, f.args.first)))
622
- else
623
- super
624
- end
625
- end
626
-
627
617
  # MSSQL uses the CONTAINS keyword for full text search
628
618
  def full_text_search(cols, terms, opts = OPTS)
629
619
  terms = "\"#{terms.join('" OR "')}\"" if terms.is_a?(Array)
@@ -825,6 +815,23 @@ module Sequel
825
815
  end
826
816
  alias update_from_sql delete_from2_sql
827
817
 
818
+ # There is no function on Microsoft SQL Server that does character length
819
+ # and respects trailing spaces (datalength respects trailing spaces, but
820
+ # counts bytes instead of characters). Use a hack to work around the
821
+ # trailing spaces issue.
822
+ def emulate_function?(name)
823
+ name == :char_length || name == :trim
824
+ end
825
+
826
+ def emulate_function_sql_append(sql, f)
827
+ case f.name
828
+ when :char_length
829
+ literal_append(sql, SQL::Function.new(:len, Sequel.join([f.args.first, 'x'])) - 1)
830
+ when :trim
831
+ literal_append(sql, SQL::Function.new(:ltrim, SQL::Function.new(:rtrim, f.args.first)))
832
+ end
833
+ end
834
+
828
835
  # Microsoft SQL Server 2012 has native support for offsets, but only for ordered datasets.
829
836
  def emulate_offset_with_row_number?
830
837
  super && !(is_2012_or_later? && @opts[:order])
@@ -129,12 +129,12 @@ module Sequel
129
129
  true
130
130
  end
131
131
 
132
- # MySQL supports prepared transactions (two-phase commit) using XA
132
+ # MySQL 5+ supports prepared transactions (two-phase commit) using XA
133
133
  def supports_prepared_transactions?
134
134
  server_version >= 50000
135
135
  end
136
136
 
137
- # MySQL supports savepoints
137
+ # MySQL 5+ supports savepoints
138
138
  def supports_savepoints?
139
139
  server_version >= 50000
140
140
  end
@@ -540,6 +540,11 @@ module Sequel
540
540
  def type_literal_generic_trueclass(column)
541
541
  :'tinyint(1)'
542
542
  end
543
+
544
+ # MySQL 5.0.2+ supports views with check option.
545
+ def view_with_check_option_support
546
+ :local if server_version >= 50002
547
+ end
543
548
  end
544
549
 
545
550
  # Dataset methods shared by datasets that use MySQL databases.
@@ -755,6 +760,11 @@ module Sequel
755
760
  sql << BACKTICK << c.to_s.gsub(BACKTICK_RE, DOUBLE_BACKTICK) << BACKTICK
756
761
  end
757
762
 
763
+ # MySQL does not support derived column lists
764
+ def supports_derived_column_lists?
765
+ false
766
+ end
767
+
758
768
  # MySQL can emulate DISTINCT ON with its non-standard GROUP BY implementation,
759
769
  # though the rows returned cannot be made deterministic through ordering.
760
770
  def supports_distinct_on?
@@ -244,6 +244,11 @@ module Sequel
244
244
  def uses_clob_for_text?
245
245
  true
246
246
  end
247
+
248
+ # Oracle supports views with check option, but not local.
249
+ def view_with_check_option_support
250
+ true
251
+ end
247
252
  end
248
253
 
249
254
  module DatasetMethods
@@ -295,20 +300,6 @@ module Sequel
295
300
  end
296
301
  end
297
302
 
298
- # Oracle treats empty strings like NULL values, and doesn't support
299
- # char_length, so make char_length use length with a nonempty string.
300
- # Unfortunately, as Oracle treats the empty string as NULL, there is
301
- # no way to get trim to return an empty string instead of nil if
302
- # the string only contains spaces.
303
- def emulated_function_sql_append(sql, f)
304
- case f.f
305
- when :char_length
306
- literal_append(sql, Sequel::SQL::Function.new(:length, Sequel.join([f.args.first, 'x'])) - 1)
307
- else
308
- super
309
- end
310
- end
311
-
312
303
  # Oracle uses MINUS instead of EXCEPT, and doesn't support EXCEPT ALL
313
304
  def except(dataset, opts=OPTS)
314
305
  raise(Sequel::Error, "EXCEPT ALL not supported") if opts[:all]
@@ -373,6 +364,11 @@ module Sequel
373
364
  type == :select
374
365
  end
375
366
 
367
+ # Oracle does not support derived column lists
368
+ def supports_derived_column_lists?
369
+ false
370
+ end
371
+
376
372
  # Oracle supports GROUP BY CUBE
377
373
  def supports_group_cube?
378
374
  true
@@ -427,7 +423,8 @@ module Sequel
427
423
 
428
424
  # Oracle doesn't support the use of AS when aliasing a dataset. It doesn't require
429
425
  # the use of AS anywhere, so this disables it in all cases.
430
- def as_sql_append(sql, aliaz)
426
+ def as_sql_append(sql, aliaz, column_aliases=nil)
427
+ raise Error, "oracle does not support derived column lists" if column_aliases
431
428
  sql << SPACE
432
429
  quote_identifier_append(sql, aliaz)
433
430
  end
@@ -441,6 +438,25 @@ module Sequel
441
438
  DUAL
442
439
  end
443
440
 
441
+ # There is no function on Microsoft SQL Server that does character length
442
+ # and respects trailing spaces (datalength respects trailing spaces, but
443
+ # counts bytes instead of characters). Use a hack to work around the
444
+ # trailing spaces issue.
445
+ def emulate_function?(name)
446
+ name == :char_length
447
+ end
448
+
449
+ # Oracle treats empty strings like NULL values, and doesn't support
450
+ # char_length, so make char_length use length with a nonempty string.
451
+ # Unfortunately, as Oracle treats the empty string as NULL, there is
452
+ # no way to get trim to return an empty string instead of nil if
453
+ # the string only contains spaces.
454
+ def emulate_function_sql_append(sql, f)
455
+ if f.name == :char_length
456
+ literal_append(sql, Sequel::SQL::Function.new(:length, Sequel.join([f.args.first, 'x'])) - 1)
457
+ end
458
+ end
459
+
444
460
  # If this dataset is associated with a sequence, return the most recently
445
461
  # inserted sequence value.
446
462
  def execute_insert(sql, opts=OPTS)
@@ -402,8 +402,10 @@ module Sequel
402
402
  #
403
403
  # DB.refresh_view(:items_view)
404
404
  # # REFRESH MATERIALIZED VIEW items_view
405
+ # DB.refresh_view(:items_view, :concurrently=>true)
406
+ # # REFRESH MATERIALIZED VIEW CONCURRENTLY items_view
405
407
  def refresh_view(name, opts=OPTS)
406
- run "REFRESH MATERIALIZED VIEW #{quote_schema_table(name)}"
408
+ run "REFRESH MATERIALIZED VIEW#{' CONCURRENTLY' if opts[:concurrently]} #{quote_schema_table(name)}"
407
409
  end
408
410
 
409
411
  # Reset the database's conversion procs, requires a server query if there
@@ -588,6 +590,15 @@ module Sequel
588
590
  end
589
591
  end
590
592
 
593
+ # Set the READ ONLY transaction setting per savepoint, as PostgreSQL supports that.
594
+ def begin_savepoint(conn, opts)
595
+ super
596
+
597
+ unless (read_only = opts[:read_only]).nil?
598
+ log_connection_execute(conn, "SET TRANSACTION READ #{read_only ? 'ONLY' : 'WRITE'}")
599
+ end
600
+ end
601
+
591
602
  # Handle PostgreSQL specific default format.
592
603
  def column_schema_normalize_default(default, type)
593
604
  if m = POSTGRES_DEFAULT_RE.match(default)
@@ -1094,6 +1105,11 @@ module Sequel
1094
1105
  :text
1095
1106
  end
1096
1107
  end
1108
+
1109
+ # PostgreSQL 9.4+ supports views with check option.
1110
+ def view_with_check_option_support
1111
+ :local if server_version >= 90400
1112
+ end
1097
1113
  end
1098
1114
 
1099
1115
  # Instance methods for datasets that connect to a PostgreSQL database.
@@ -1227,17 +1243,25 @@ module Sequel
1227
1243
  # contains all of the words in terms. This ignores search operators in terms.
1228
1244
  # :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
1229
1245
  # returned rows also include the exact phrase used.
1246
+ # :rank :: Set to true to order by the rank, so that closer matches are returned first.
1230
1247
  def full_text_search(cols, terms, opts = OPTS)
1231
- lang = opts[:language] || 'simple'
1248
+ lang = Sequel.cast(opts[:language] || 'simple', :regconfig)
1232
1249
  terms = terms.join(' | ') if terms.is_a?(Array)
1233
- to_tsquery = (opts[:phrase] || opts[:plain]) ? 'plainto_tsquery' : 'to_tsquery'
1250
+ columns = full_text_string_join(cols)
1251
+ query_func = (opts[:phrase] || opts[:plain]) ? :plainto_tsquery : :to_tsquery
1252
+ vector = Sequel.function(:to_tsvector, lang, columns)
1253
+ query = Sequel.function(query_func, lang, terms)
1234
1254
 
1235
- ds = where(Sequel.lit(["(to_tsvector(", "::regconfig, ", ") @@ #{to_tsquery}(", "::regconfig, ", "))"], lang, full_text_string_join(cols), lang, terms))
1255
+ ds = where(Sequel.lit(["(", " @@ ", ")"], vector, query))
1236
1256
 
1237
1257
  if opts[:phrase]
1238
1258
  ds = ds.grep(cols, "%#{escape_like(terms)}%", :case_insensitive=>true)
1239
1259
  end
1240
1260
 
1261
+ if opts[:rank]
1262
+ ds = ds.order{ts_rank_cd(vector, query)}
1263
+ end
1264
+
1241
1265
  ds
1242
1266
  end
1243
1267