sequel 4.7.0 → 4.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +46 -0
  3. data/README.rdoc +25 -1
  4. data/doc/active_record.rdoc +1 -1
  5. data/doc/advanced_associations.rdoc +143 -17
  6. data/doc/association_basics.rdoc +80 -59
  7. data/doc/release_notes/4.8.0.txt +175 -0
  8. data/lib/sequel/adapters/odbc.rb +1 -1
  9. data/lib/sequel/adapters/odbc/mssql.rb +4 -2
  10. data/lib/sequel/adapters/shared/postgres.rb +19 -3
  11. data/lib/sequel/adapters/shared/sqlite.rb +3 -3
  12. data/lib/sequel/ast_transformer.rb +1 -1
  13. data/lib/sequel/dataset/actions.rb +1 -1
  14. data/lib/sequel/dataset/graph.rb +23 -9
  15. data/lib/sequel/dataset/misc.rb +2 -2
  16. data/lib/sequel/dataset/sql.rb +3 -3
  17. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  18. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +1 -1
  19. data/lib/sequel/extensions/pg_array.rb +1 -1
  20. data/lib/sequel/extensions/pg_array_ops.rb +6 -0
  21. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -0
  22. data/lib/sequel/extensions/pg_json_ops.rb +5 -0
  23. data/lib/sequel/extensions/query.rb +8 -2
  24. data/lib/sequel/extensions/to_dot.rb +1 -1
  25. data/lib/sequel/model/associations.rb +476 -152
  26. data/lib/sequel/plugins/class_table_inheritance.rb +11 -3
  27. data/lib/sequel/plugins/dataset_associations.rb +21 -18
  28. data/lib/sequel/plugins/many_through_many.rb +87 -20
  29. data/lib/sequel/plugins/nested_attributes.rb +12 -0
  30. data/lib/sequel/plugins/pg_array_associations.rb +31 -12
  31. data/lib/sequel/plugins/single_table_inheritance.rb +9 -1
  32. data/lib/sequel/sql.rb +1 -0
  33. data/lib/sequel/version.rb +1 -1
  34. data/spec/adapters/mssql_spec.rb +2 -2
  35. data/spec/adapters/postgres_spec.rb +7 -0
  36. data/spec/core/object_graph_spec.rb +250 -196
  37. data/spec/extensions/core_refinements_spec.rb +1 -1
  38. data/spec/extensions/dataset_associations_spec.rb +100 -6
  39. data/spec/extensions/many_through_many_spec.rb +1002 -19
  40. data/spec/extensions/nested_attributes_spec.rb +24 -0
  41. data/spec/extensions/pg_array_associations_spec.rb +17 -12
  42. data/spec/extensions/pg_array_spec.rb +4 -2
  43. data/spec/extensions/spec_helper.rb +1 -1
  44. data/spec/integration/associations_test.rb +1003 -48
  45. data/spec/integration/dataset_test.rb +12 -5
  46. data/spec/integration/prepared_statement_test.rb +1 -1
  47. data/spec/integration/type_test.rb +1 -1
  48. data/spec/model/associations_spec.rb +467 -130
  49. data/spec/model/eager_loading_spec.rb +332 -5
  50. metadata +5 -3
@@ -0,0 +1,175 @@
1
+ = New Features
2
+
3
+ * A one_through_one association type has been added. This is similar
4
+ to the many_to_many association type in that it uses a join table,
5
+ but it returns a single record instead of an array of records.
6
+ This is designed for cases where the foreign key in the join table
7
+ that references the current table has a unique constraint, or where
8
+ you want to use an order to just pick the first matching record.
9
+
10
+ Similarly, the many_through_many plugin now also offers a
11
+ one_through_many association.
12
+
13
+ * An association_join method has been added to model datasets, for
14
+ setting up joins based on associations. This basically does the
15
+ same join that eager_graph would do, but does not make the other
16
+ changes that eager_graph makes.
17
+
18
+ Unlike eager_graph (which uses LEFT OUTER JOINs by default),
19
+ association_join uses INNER JOINs, but there are also
20
+ association_*_join methods (e.g. association_left_join) for
21
+ using different join types.
22
+
23
+ Similar to eager_graph, you can use cascading of associations or
24
+ multiple associations.
25
+
26
+ Album.association_join(:artist, :tracks)
27
+ Artist.association_left_join(:albums=>:tracks)
28
+
29
+ * Dataset#eager_graph_with_options has been added for model
30
+ datasets. It currently supports a :join_type option, for
31
+ overriding the type of join to use on a per-call basis, as well
32
+ as a :limit_strategy option. The API is similar to eager_graph,
33
+ except that the associations to eagerly load are passed in as
34
+ a single argument, and it takes an options hash.
35
+
36
+ The :limit_strategy option works similarly to the
37
+ :eager_limit_strategy option when eagerly loading. If set to
38
+ true and the database supports window functions, it will join
39
+ the current dataset to a subquery that uses a window function
40
+ to correctly restrict the join to only those objects that fall
41
+ within the association's limit/offset.
42
+
43
+ The :limit_strategy option is not on by default. It is possible
44
+ for it to perform significantly worse than the default strategy
45
+ (which uses array slicing in ruby). The :limit_strategy
46
+ significantly changes the SQL used, and can change the results
47
+ of the query if any filters/orders related to the association
48
+ are used.
49
+
50
+ It's recommended you only use the :limit_strategy option if you
51
+ are experiencing a bottleneck and you have benchmarked that it
52
+ is faster and still produces the desired results.
53
+
54
+ Artist.eager_graph_with_options(:first_10_albums,
55
+ :limit_strategy=>true)
56
+ # SELECT artists.id, artists.name,
57
+ # first_10_albums.id AS first_10_albums_id,
58
+ # first_10_albums.name AS first_10_albums_name,
59
+ # first_10_albums.artist_id,
60
+ # first_10_albums.release_date
61
+ # FROM artists
62
+ # LEFT OUTER JOIN (
63
+ # SELECT id, name, artist_id, release_date
64
+ # FROM (
65
+ # SELECT *, row_number() OVER (PARTITION BY tracks.album_id)
66
+ # AS x_sequel_row_number_x
67
+ # FROM albums
68
+ # ) AS t1 WHERE (x_sequel_row_number_x <= 10)
69
+ # ) AS first_10_albums ON (first_10_albums.artist_id = artists.id)
70
+
71
+ * Dataset#full_text_search on PostgreSQL now supports :plain and
72
+ :phrase options. :plain takes the search terms as a single
73
+ string, and searches for rows where all terms are used.
74
+ :phrase is similar to :plain, but also adds a substring search
75
+ to ensure that the string given appears verbatim in the text.
76
+
77
+ * A :graph_order association option has been added, for using a
78
+ different order when using eager_graph. This is mostly
79
+ designed for cases where :order should be qualified in other
80
+ cases, but using a qualification breaks eager_graph because the
81
+ correct qualifier is not known until runtime.
82
+
83
+ * SQL::AliasedExpression#alias has been added as an alias for #aliaz.
84
+
85
+ = Other Improvements
86
+
87
+ * Sequel will now automatically use an eager limit strategy for
88
+ *_one associations that use an :order option. For associations
89
+ that are truly one-to-one, an :order option is not needed, so it
90
+ only makes sense to have an :order option if the association
91
+ could theoretically return multiple results (in which case an
92
+ eager limit strategy is helpful).
93
+
94
+ * The queries that Sequel uses to filter by associations when
95
+ those associations have conditions are now simpler and easier
96
+ for the database to execute.
97
+
98
+ * The queries that Sequel uses for dataset associations now handle
99
+ cases where unqualified identifiers were used in the receiving
100
+ dataset that would be made ambiguous by a join.
101
+
102
+ * A limit strategy is now used when filtering by associations if
103
+ the association has a limit and the database supports window
104
+ functions. This allows Sequel to setup a correct filter in
105
+ such cases.
106
+
107
+ Artist.where(:first_10_albums=>Album[1]).all
108
+ # SELECT *
109
+ # FROM artists
110
+ # WHERE (artists.id IN (
111
+ # SELECT albums.artist_id
112
+ # FROM albums
113
+ # WHERE ((albums.artist_id IS NOT NULL) AND (albums.id IN (
114
+ # SELECT id FROM (
115
+ # SELECT albums.id, row_number() OVER
116
+ # (PARTITION BY albums.artist_id ORDER BY release_date)
117
+ # AS x_sequel_row_number_x
118
+ # FROM albums
119
+ # ) AS t1
120
+ # WHERE (x_sequel_row_number_x <= 10)
121
+ # )) AND (albums.id = 1))))
122
+
123
+ * A limit strategy is now used in the dataset_associations plugin
124
+ if the association has a limit and the database supports window
125
+ functions. This makes the resulting datasets return correct
126
+ results.
127
+
128
+ Artist.first_10_albums
129
+ # SELECT *
130
+ # FROM albums
131
+ # WHERE ((albums.artist_id IN (
132
+ # SELECT artists.id FROM artists)
133
+ # ) AND (albums.id IN (
134
+ # SELECT id FROM (
135
+ # SELECT albums.id, row_number() OVER
136
+ # (PARTITION BY albums.artist_id ORDER BY release_date)
137
+ # AS x_sequel_row_number_x
138
+ # FROM albums
139
+ # ) AS t1
140
+ # WHERE (x_sequel_row_number_x <= 10)
141
+ # )))
142
+ # ORDER BY release_date
143
+
144
+ * You can now pass symbols with embedded qualifiers or aliases,
145
+ as well as SQL::Identifier, SQL::QualifiedIdentifier, and
146
+ SQL::AliasedExpression objects as the first argument to
147
+ Dataset#graph.
148
+
149
+ * The nested_attributes plugin now automatically handles presence
150
+ validations on foreign keys when creating associated objects.
151
+ It now sets the foreign key value (or a placeholder value)
152
+ before validating such objects.
153
+
154
+ * Offsets on *_one associations are now respected when using
155
+ eager_graph.
156
+
157
+ * eager graphing *_many associations with offsets no longer breaks
158
+ if there are no associated results.
159
+
160
+ * Database#register_array_type in the pg_array extension now works
161
+ correctly if there is no existing scalar conversion proc for
162
+ the type.
163
+
164
+ * Unique, foreign key, and not null constraint violations are now
165
+ recognized correctly on SQLite 3.8.2+.
166
+
167
+ * The odbc adapter now returns fractional seconds in timestamps.
168
+
169
+ * The obdc/mssql adapter now inputs timestamps with 3 decimal
170
+ places.
171
+
172
+ = Backwards Compatibility
173
+
174
+ * The private Model.apply_window_function_eager_limit_strategy
175
+ method has been removed.
@@ -127,7 +127,7 @@ module Sequel
127
127
  # ODBCColumn#mapSqlTypeToGenericType and Column#klass.
128
128
  case v
129
129
  when ::ODBC::TimeStamp
130
- db.to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second])
130
+ db.to_application_timestamp([v.year, v.month, v.day, v.hour, v.minute, v.second, v.fraction])
131
131
  when ::ODBC::Time
132
132
  Sequel::SQLTime.create(v.hour, v.minute, v.second)
133
133
  when ::ODBC::Date
@@ -32,10 +32,12 @@ module Sequel
32
32
  class Dataset < ODBC::Dataset
33
33
  include Sequel::MSSQL::DatasetMethods
34
34
 
35
+ # Use ODBC format, not Microsoft format, as the ODBC layer does
36
+ # some translation. MSSQL version is over-ridden to allow 3 millisecond decimal places
37
+ TIMESTAMP_FORMAT="{ts '%Y-%m-%d %H:%M:%S%N'}".freeze
38
+
35
39
  private
36
40
 
37
- # Use ODBC format, not Microsoft format, as the ODBC layer does
38
- # some translation.
39
41
  def default_timestamp_format
40
42
  TIMESTAMP_FORMAT
41
43
  end
@@ -1193,12 +1193,28 @@ module Sequel
1193
1193
  lock_style(:share)
1194
1194
  end
1195
1195
 
1196
- # PostgreSQL specific full text search syntax, using tsearch2 (included
1197
- # in 8.3 by default, and available for earlier versions as an add-on).
1196
+ # Run a full text search on PostgreSQL. By default, searching for the inclusion
1197
+ # of any of the terms in any of the cols.
1198
+ #
1199
+ # Options:
1200
+ # :language :: The language to use for the search (default: 'simple')
1201
+ # :plain :: Whether a plain search should be used (default: false). In this case,
1202
+ # terms should be a single string, and it will do a search where cols
1203
+ # contains all of the words in terms. This ignores search operators in terms.
1204
+ # :phrase :: Similar to :plain, but also adding an ILIKE filter to ensure that
1205
+ # returned rows also include the exact phrase used.
1198
1206
  def full_text_search(cols, terms, opts = OPTS)
1199
1207
  lang = opts[:language] || 'simple'
1200
1208
  terms = terms.join(' | ') if terms.is_a?(Array)
1201
- filter("to_tsvector(?::regconfig, ?) @@ to_tsquery(?::regconfig, ?)", lang, full_text_string_join(cols), lang, terms)
1209
+ to_tsquery = (opts[:phrase] || opts[:plain]) ? 'plainto_tsquery' : 'to_tsquery'
1210
+
1211
+ ds = where(Sequel.lit(["(to_tsvector(", "::regconfig, ", ") @@ #{to_tsquery}(", "::regconfig, ", "))"], lang, full_text_string_join(cols), lang, terms))
1212
+
1213
+ if opts[:phrase]
1214
+ ds = ds.grep(cols, "%#{escape_like(terms)}%", :case_insensitive=>true)
1215
+ end
1216
+
1217
+ ds
1202
1218
  end
1203
1219
 
1204
1220
  # Insert given values into the database.
@@ -332,10 +332,10 @@ module Sequel
332
332
  end
333
333
 
334
334
  DATABASE_ERROR_REGEXPS = {
335
- /(is|are) not unique\z/ => UniqueConstraintViolation,
336
- /foreign key constraint failed\z/ => ForeignKeyConstraintViolation,
335
+ /(is|are) not unique\z|UNIQUE constraint failed: .+\z/ => UniqueConstraintViolation,
336
+ /foreign key constraint failed\z|FOREIGN KEY constraint failed\z/ => ForeignKeyConstraintViolation,
337
337
  /\A(SQLITE ERROR 19 \(CONSTRAINT\) : )?constraint failed\z/ => ConstraintViolation,
338
- /may not be NULL\z/ => NotNullConstraintViolation,
338
+ /may not be NULL\z|NOT NULL constraint failed: .+\z/ => NotNullConstraintViolation,
339
339
  }.freeze
340
340
  def database_error_regexps
341
341
  DATABASE_ERROR_REGEXPS
@@ -33,7 +33,7 @@ module Sequel
33
33
  when SQL::OrderedExpression
34
34
  SQL::OrderedExpression.new(v(o.expression), o.descending, :nulls=>o.nulls)
35
35
  when SQL::AliasedExpression
36
- SQL::AliasedExpression.new(v(o.expression), o.aliaz)
36
+ SQL::AliasedExpression.new(v(o.expression), o.alias)
37
37
  when SQL::CaseExpression
38
38
  args = [v(o.conditions), v(o.default)]
39
39
  args << v(o.expression) if o.expression?
@@ -825,7 +825,7 @@ module Sequel
825
825
  when SQL::QualifiedIdentifier
826
826
  _hash_key_symbol(s.column, true)
827
827
  when SQL::AliasedExpression
828
- _hash_key_symbol(s.aliaz, true)
828
+ _hash_key_symbol(s.alias, true)
829
829
  when String
830
830
  s.to_sym if recursing
831
831
  end
@@ -26,7 +26,7 @@ module Sequel
26
26
  #
27
27
  # Arguments:
28
28
  # dataset :: Can be a symbol (specifying a table), another dataset,
29
- # or an object that responds to +dataset+ and returns a symbol or a dataset
29
+ # or an SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression.
30
30
  # join_conditions :: Any condition(s) allowed by +join_table+.
31
31
  # block :: A block that is passed to +join_table+.
32
32
  #
@@ -50,22 +50,36 @@ module Sequel
50
50
  # Allow the use of a dataset or symbol as the first argument
51
51
  # Find the table name/dataset based on the argument
52
52
  table_alias = options[:table_alias]
53
+ table = dataset
54
+ create_dataset = true
55
+
53
56
  case dataset
54
57
  when Symbol
55
- table = dataset
56
- dataset = @db[dataset]
57
- table_alias ||= table
58
- when ::Sequel::Dataset
58
+ # let alias be the same as the table name (sans any optional schema)
59
+ # unless alias explicitly given in the symbol using ___ notation
60
+ table_alias ||= split_symbol(table).compact.last
61
+ when Dataset
59
62
  if dataset.simple_select_all?
60
63
  table = dataset.opts[:from].first
61
64
  table_alias ||= table
62
65
  else
63
- table = dataset
64
66
  table_alias ||= dataset_alias((@opts[:num_dataset_sources] || 0)+1)
65
67
  end
68
+ create_dataset = false
69
+ when SQL::Identifier
70
+ table_alias ||= table.value
71
+ when SQL::QualifiedIdentifier
72
+ table_alias ||= split_qualifiers(table).last
73
+ when SQL::AliasedExpression
74
+ return graph(table.expression, join_conditions, {:table_alias=>table.alias}.merge(options), &block)
66
75
  else
67
76
  raise Error, "The dataset argument should be a symbol or dataset"
68
77
  end
78
+ table_alias = table_alias.to_sym
79
+
80
+ if create_dataset
81
+ dataset = db.from(table)
82
+ end
69
83
 
70
84
  # Raise Sequel::Error with explanation that the table alias has been used
71
85
  raise_alias_error = lambda do
@@ -76,8 +90,8 @@ module Sequel
76
90
  # Only allow table aliases that haven't been used
77
91
  raise_alias_error.call if @opts[:graph] && @opts[:graph][:table_aliases] && @opts[:graph][:table_aliases].include?(table_alias)
78
92
 
79
- # Use a from_self if this is already a joined table
80
- ds = (!@opts[:graph] && (@opts[:from].length > 1 || @opts[:join])) ? from_self(:alias=>options[:from_self_alias] || first_source) : self
93
+ # Use a from_self if this is already a joined table (or from_self specifically disabled for graphs)
94
+ ds = (@opts[:graph_from_self] != false && !@opts[:graph] && (@opts[:from].length > 1 || @opts[:join])) ? from_self(:alias=>options[:from_self_alias] || first_source) : self
81
95
 
82
96
  # Join the table early in order to avoid cloning the dataset twice
83
97
  ds = ds.join_table(options[:join_type] || :left_outer, table, join_conditions, :table_alias=>table_alias, :implicit_qualifier=>options[:implicit_qualifier], :qualify=>options[:qualify], &block)
@@ -121,7 +135,7 @@ module Sequel
121
135
  column = column.value if column.is_a?(SQL::Identifier)
122
136
  column.to_sym
123
137
  when SQL::AliasedExpression
124
- column = sel.aliaz
138
+ column = sel.alias
125
139
  column = column.value if column.is_a?(SQL::Identifier)
126
140
  column.to_sym
127
141
  else
@@ -97,7 +97,7 @@ module Sequel
97
97
  end
98
98
  case s = source.first
99
99
  when SQL::AliasedExpression
100
- s.aliaz
100
+ s.alias
101
101
  when Symbol
102
102
  _, _, aliaz = split_symbol(s)
103
103
  aliaz ? aliaz.to_sym : s
@@ -178,7 +178,7 @@ module Sequel
178
178
  c_table, column, aliaz = split_symbol(c)
179
179
  [c_table ? SQL::QualifiedIdentifier.new(c_table, column.to_sym) : column.to_sym, aliaz]
180
180
  when SQL::AliasedExpression
181
- [c.expression, c.aliaz]
181
+ [c.expression, c.alias]
182
182
  when SQL::JoinClause
183
183
  [c.table, c.table_alias]
184
184
  else
@@ -298,7 +298,7 @@ module Sequel
298
298
  # SQL fragment for AliasedExpression
299
299
  def aliased_expression_sql_append(sql, ae)
300
300
  literal_append(sql, ae.expression)
301
- as_sql_append(sql, ae.aliaz)
301
+ as_sql_append(sql, ae.alias)
302
302
  end
303
303
 
304
304
  # SQL fragment for Array
@@ -801,7 +801,7 @@ module Sequel
801
801
  when SQL::QualifiedIdentifier
802
802
  alias_symbol(sym.column)
803
803
  when SQL::AliasedExpression
804
- alias_alias_symbol(sym.aliaz)
804
+ alias_alias_symbol(sym.alias)
805
805
  else
806
806
  raise Error, "Invalid alias for alias_symbol: #{sym.inspect}"
807
807
  end
@@ -1183,7 +1183,7 @@ module Sequel
1183
1183
  schema, table, t_alias = split_symbol(table)
1184
1184
  t_alias ||= Sequel::SQL::QualifiedIdentifier.new(schema, table) if schema
1185
1185
  when Sequel::SQL::AliasedExpression
1186
- t_alias = table.aliaz
1186
+ t_alias = table.alias
1187
1187
  end
1188
1188
  c_table = t_alias || table
1189
1189
  end
@@ -71,7 +71,7 @@ module Sequel
71
71
  col = c.column
72
72
  col.is_a?(SQL::Identifier) ? col.value.to_sym : col.to_sym
73
73
  when SQL::AliasedExpression
74
- a = c.aliaz
74
+ a = c.alias
75
75
  a.is_a?(SQL::Identifier) ? a.value.to_sym : a.to_sym
76
76
  end
77
77
  end
@@ -58,7 +58,7 @@ module Sequel
58
58
  ds = from(*source)
59
59
  lateral.each do |l|
60
60
  l = if l.is_a?(Sequel::SQL::AliasedExpression)
61
- l.expression.clone(:lateral=>nil).as(l.aliaz)
61
+ l.expression.clone(:lateral=>nil).as(l.alias)
62
62
  else
63
63
  l.clone(:lateral=>nil)
64
64
  end
@@ -185,7 +185,7 @@ module Sequel
185
185
 
186
186
  if soid = opts[:scalar_oid]
187
187
  raise Error, "can't provide both a converter and :scalar_oid option to register" if converter
188
- raise Error, "no conversion proc for :scalar_oid=>#{soid.inspect}" unless converter = type_procs[soid]
188
+ converter = type_procs[soid]
189
189
  end
190
190
 
191
191
  array_type = (opts[:array_type] || db_type).to_s.dup.freeze
@@ -42,6 +42,8 @@
42
42
  # ia.any # ANY(int_array_column)
43
43
  # ia.all # ALL(int_array_column)
44
44
  # ia.dims # array_dims(int_array_column)
45
+ # ia.hstore # hstore(int_array_column)
46
+ # ia.hstore(:a) # hstore(int_array_column, a)
45
47
  # ia.length # array_length(int_array_column, 1)
46
48
  # ia.length(2) # array_length(int_array_column, 2)
47
49
  # ia.lower # array_lower(int_array_column, 1)
@@ -57,6 +59,10 @@
57
59
  # If you are also using the pg_array extension, you should load it before
58
60
  # loading this extension. Doing so will allow you to use PGArray#op to get
59
61
  # an ArrayOp, allowing you to perform array operations on array literals.
62
+ #
63
+ # In order for #hstore to automatically wrap the returned value correctly in
64
+ # an HStoreOp, you need to load the pg_hstore_ops extension.
65
+
60
66
  module Sequel
61
67
  module Postgres
62
68
  # The ArrayOp class is a simple container for a single object that
@@ -58,6 +58,13 @@
58
58
  # If you are also using the pg_hstore extension, you should load it before
59
59
  # loading this extension. Doing so will allow you to use HStore#op to get
60
60
  # an HStoreOp, allowing you to perform hstore operations on hstore literals.
61
+ #
62
+ # Some of these methods will accept ruby arrays and convert them automatically to
63
+ # PostgreSQL arrays if you have the pg_array extension loaded. Some of these methods
64
+ # will accept ruby hashes and convert them automatically to PostgreSQL hstores if the
65
+ # pg_hstore extension is loaded. Methods representing expressions that return
66
+ # PostgreSQL arrays will have the returned expression automatically wrapped in a
67
+ # Postgres::ArrayOp if the pg_array_ops extension is loaded.
61
68
 
62
69
  module Sequel
63
70
  module Postgres
@@ -49,6 +49,11 @@
49
49
  # loading this extension. Doing so will allow you to use JSONHash#op and
50
50
  # JSONArray#op to get a JSONOp, allowing you to perform json operations
51
51
  # on json literals.
52
+ #
53
+ # In order to get the automatic conversion from a ruby array to a PostgreSQL array
54
+ # (as shown in the #[] and #get_text examples above), you need to load the pg_array
55
+ # extension.
56
+
52
57
  module Sequel
53
58
  module Postgres
54
59
  # The JSONOp class is a simple container for a single object that