sequel 5.55.0 → 5.56.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dfed01eed8d575d13963be4568c8935f82c47a59b2be32d382439d50153f032d
4
- data.tar.gz: 9ac182c00cdc1f54c26626005f990c5e0a05eebaae3b3c5b88bac0fcd770cd8e
3
+ metadata.gz: 3505371d31fd90d388e3d889117e0dc949e73d41ebd6bee01441be9d061ea33a
4
+ data.tar.gz: fed52ae3813a0799065e695e90ab95e9633195477d01d1df9de6e6dbddbcaa23
5
5
  SHA512:
6
- metadata.gz: c9aceee8528a749c541f74eeb07610b520e4342ac8d11abda3222fd0d760e2ff2a16e1b08b7931f5d24cb0ae9fced020ef695798ffac6c4b4af15508e8927468
7
- data.tar.gz: 19e40f6386373bdbba07b5a65faf5ffc99a5693e81b0eaeaf1abffc9b07cc29c2688ea658629f633eb36a3427f74857cab28216d18cfb208cca97a75ea10788e
6
+ metadata.gz: 6bdd85a8fbd1ba0c0fc871e2e318836e7463d73f9677c0fbba667b6bc86fbe0cb6f0fe09026a39b9b9c79aa783c60f211ffd993b22dff6f9e0d43d3e18754351
7
+ data.tar.gz: 6ce33f8f9593e7cf73d1322104b7dd362deca59eb6397e7d4ea2705c8bb17d4087453903edd59389181d9829818fd31dc2b990dc77b4c3f3a714b846c8a5db3b
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ === 5.56.0 (2022-05-01)
2
+
3
+ * Make alter_table add_column/add_foreign_key methods support :index option to create an index on the column (jeremyevans)
4
+
5
+ * Support creation of STRICT tables on SQLite 3.37.0+ via create_table :strict option (jeremyevans)
6
+
7
+ * Add sqlite_json_ops extension for DSL support for JSON functions and operators added in SQLite 3.38.0 (jeremyevans)
8
+
9
+ * Recognize "INTEGER" type same as "integer" type in the schema dumper, helpful on SQLite 3.37.0+ (jeremyevans)
10
+
1
11
  === 5.55.0 (2022-04-01)
2
12
 
3
13
  * Support :setup_regexp_function Database option in the sqlite adapter to allow the use of regexps when querying (jeremyevans)
@@ -0,0 +1,51 @@
1
+ = New Features
2
+
3
+ * On SQLite, Database#create_table now supports a :strict option to
4
+ use the STRICT keyword when creating the table. When this option
5
+ is used, SQLite will enforce the types for each column. When using
6
+ this option, you are limited to using the following column types:
7
+ int, integer, real, text, blob, and any (any allows for dynamic
8
+ types).
9
+
10
+ * An sqlite_json_ops extension has been added, providing DSL support
11
+ for JSON functions and operators supported in SQLite 3.38.0. Usage
12
+ is similar to the pg_json_ops extension. First, you create an
13
+ appropriate object:
14
+
15
+ j = Sequel.sqlite_json_op(:json_column)
16
+ # or:
17
+ j = Sequel[:json_column].sqlite_json_op
18
+
19
+ Then, you call methods on that object to create expressions for the
20
+ JSON functions and operators:
21
+
22
+ j[1] # (json_column ->> 1)
23
+ j.get_text(1) # (json_column -> 1)
24
+ j.extract('$.a') # json_extract(json_column, '$.a')
25
+
26
+ j.array_length # json_array_length(json_column)
27
+ j.type # json_type(json_column)
28
+ j.valid # json_valid(json_column)
29
+ j.json # json(json_column)
30
+
31
+ j.insert('$.a', 1) # json_insert(json_column, '$.a', 1)
32
+ j.set('$.a', 1) # json_set(json_column, '$.a', 1)
33
+ j.replace('$.a', 1) # json_replace(json_column, '$.a', 1)
34
+ j.remove('$.a') # json_remove(json_column, '$.a')
35
+ j.patch('{"a":2}') # json_patch(json_column, '{"a":2}')
36
+
37
+ j.each # json_each(json_column)
38
+ j.tree # json_tree(json_column)
39
+
40
+ = Other Improvements
41
+
42
+ * The alter_table add_column and add_foreign_key methods now support
43
+ the :index option to create an index on the added column, for
44
+ compatibility with the :index option on the create_table column and
45
+ foreign_key methods.
46
+
47
+ * The schema_dumper extension now treats the "INTEGER" type the same
48
+ as the "integer" type. This fixes some behavior when using SQLite
49
+ 3.37.0+.
50
+
51
+ * Sequel's website has a much improved visual design.
@@ -337,6 +337,11 @@ module Sequel
337
337
  ps
338
338
  end
339
339
 
340
+ # Support creating STRICT tables via :strict option
341
+ def create_table_sql(name, generator, options)
342
+ "#{super}#{' STRICT' if options[:strict]}"
343
+ end
344
+
340
345
  # SQLite support creating temporary views.
341
346
  def create_view_prefix_sql(name, options)
342
347
  create_view_sql_append_columns("CREATE #{'TEMPORARY 'if options[:temp]}VIEW #{quote_schema_table(name)}", options[:columns])
@@ -347,6 +352,7 @@ module Sequel
347
352
  /foreign key constraint failed\z/i => ForeignKeyConstraintViolation,
348
353
  /\A(SQLITE ERROR 275 \(CONSTRAINT_CHECK\) : )?CHECK constraint failed/ => CheckConstraintViolation,
349
354
  /\A(SQLITE ERROR 19 \(CONSTRAINT\) : )?constraint failed\z/ => ConstraintViolation,
355
+ /\Acannot store [A-Z]+ value in [A-Z]+ column / => ConstraintViolation,
350
356
  /may not be NULL\z|NOT NULL constraint failed: .+\z/ => NotNullConstraintViolation,
351
357
  /\ASQLITE ERROR \d+ \(\) : CHECK constraint failed: / => CheckConstraintViolation
352
358
  }.freeze
@@ -387,8 +387,7 @@ module Sequel
387
387
  end
388
388
 
389
389
  # Add a column with the given name, type, and opts.
390
- # See CreateTableGenerator#column for the available options (except for +:index+, use a
391
- # separate +add_index+ call to add an index for the column).
390
+ # See CreateTableGenerator#column for the available options.
392
391
  #
393
392
  # add_column(:name, String) # ADD COLUMN name varchar(255)
394
393
  #
@@ -401,7 +400,10 @@ module Sequel
401
400
  # :after :: The name of an existing column that the new column should be positioned after
402
401
  # :first :: Create this new column before all other existing columns
403
402
  def add_column(name, type, opts = OPTS)
404
- @operations << {:op => :add_column, :name => name, :type => type}.merge!(opts)
403
+ op = {:op => :add_column, :name => name, :type => type}.merge!(opts)
404
+ index_opts = op.delete(:index)
405
+ @operations << op
406
+ add_index(name, index_opts.is_a?(Hash) ? index_opts : OPTS) if index_opts
405
407
  nil
406
408
  end
407
409
 
@@ -430,8 +432,7 @@ module Sequel
430
432
  end
431
433
 
432
434
  # Add a foreign key with the given name and referencing the given table.
433
- # See CreateTableGenerator#column for the available options (except for +:index+, use a
434
- # separate +add_index+ call to add an index for the column).
435
+ # See CreateTableGenerator#column for the available options.
435
436
  #
436
437
  # You can also pass an array of column names for creating composite foreign
437
438
  # keys. In this case, it will assume the columns exist and will only add
@@ -183,6 +183,15 @@ module Sequel
183
183
  # keys.
184
184
  # :tablespace :: The tablespace to use for the table.
185
185
  #
186
+ # SQLite specific options:
187
+ # :strict :: Create a STRICT table, which checks that the values for the columns
188
+ # are the correct type (similar to all other SQL databases). Note that
189
+ # when using this option, all column types used should be one of the
190
+ # following: +int+, +integer+, +real+, +text+, +blob+, and +any+.
191
+ # The +any+ type is treated like a SQLite column in a non-strict table,
192
+ # allowing any type of data to be stored. This option is supported on
193
+ # SQLite 3.37.0+.
194
+ #
186
195
  # See <tt>Schema::CreateTableGenerator</tt> and the {"Schema Modification" guide}[rdoc-ref:doc/schema_modification.rdoc].
187
196
  def create_table(name, options=OPTS, &block)
188
197
  remove_cached_schema(name)
@@ -3,7 +3,8 @@
3
3
  # The pg_json_ops extension adds support to Sequel's DSL to make
4
4
  # it easier to call PostgreSQL JSON functions and operators (added
5
5
  # first in PostgreSQL 9.3). It also supports the JSONB functions
6
- # and operators added in PostgreSQL 9.4.
6
+ # and operators added in PostgreSQL 9.4, as well as additional
7
+ # functions and operators added in later versions.
7
8
  #
8
9
  # To load the extension:
9
10
  #
@@ -183,7 +183,7 @@ END_MIG
183
183
  if options[:single_pk] && schema_autoincrementing_primary_key?(schema)
184
184
  type_hash = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
185
185
  [:table, :key, :on_delete, :on_update, :deferrable].each{|f| type_hash[f] = schema[f] if schema[f]}
186
- if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"}
186
+ if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"} || type_hash == {:type=>"INTEGER"}
187
187
  type_hash.delete(:type)
188
188
  elsif options[:same_db] && type_hash == {:type=>type_literal_generic_bignum_symbol(type_hash).to_s}
189
189
  type_hash[:type] = :Bignum
@@ -225,7 +225,7 @@ END_MIG
225
225
  col_opts[:null] = false if schema[:allow_null] == false
226
226
  if table = schema[:table]
227
227
  [:key, :on_delete, :on_update, :deferrable].each{|f| col_opts[f] = schema[f] if schema[f]}
228
- col_opts[:type] = type unless type == Integer || type == 'integer'
228
+ col_opts[:type] = type unless type == Integer || type == 'integer' || type == 'INTEGER'
229
229
  gen.foreign_key(name, table, col_opts)
230
230
  else
231
231
  gen.column(name, type, col_opts)
@@ -0,0 +1,255 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The sqlite_json_ops extension adds support to Sequel's DSL to make
4
+ # it easier to call SQLite JSON functions and operators (added
5
+ # first in SQLite 3.38.0).
6
+ #
7
+ # To load the extension:
8
+ #
9
+ # Sequel.extension :sqlite_json_ops
10
+ #
11
+ # This extension works by calling methods on Sequel::SQLite::JSONOp objects,
12
+ # which you can create via Sequel.sqlite_json_op:
13
+ #
14
+ # j = Sequel.sqlite_json_op(:json_column)
15
+ #
16
+ # Also, on most Sequel expression objects, you can call the sqlite_json_op method
17
+ # to create a Sequel::SQLite::JSONOp object:
18
+ #
19
+ # j = Sequel[:json_column].sqlite_json_op
20
+ #
21
+ # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
22
+ # or you have loaded the core_refinements extension
23
+ # and have activated refinements for the file, you can also use Symbol#sqlite_json_op:
24
+ #
25
+ # j = :json_column.sqlite_json_op
26
+ #
27
+ # The following methods are available for Sequel::SQLite::JSONOp instances:
28
+ #
29
+ # j[1] # (json_column ->> 1)
30
+ # j.get(1) # (json_column ->> 1)
31
+ # j.get_text(1) # (json_column -> 1)
32
+ # j.extract('$.a') # json_extract(json_column, '$.a')
33
+ #
34
+ # j.array_length # json_array_length(json_column)
35
+ # j.type # json_type(json_column)
36
+ # j.valid # json_valid(json_column)
37
+ # j.json # json(json_column)
38
+ #
39
+ # j.insert('$.a', 1) # json_insert(json_column, '$.a', 1)
40
+ # j.set('$.a', 1) # json_set(json_column, '$.a', 1)
41
+ # j.replace('$.a', 1) # json_replace(json_column, '$.a', 1)
42
+ # j.remove('$.a') # json_remove(json_column, '$.a')
43
+ # j.patch('{"a":2}') # json_patch(json_column, '{"a":2}')
44
+ #
45
+ # j.each # json_each(json_column)
46
+ # j.tree # json_tree(json_column)
47
+ #
48
+ # Related modules: Sequel::SQLite::JSONOp
49
+
50
+ #
51
+ module Sequel
52
+ module SQLite
53
+ # The JSONOp class is a simple container for a single object that
54
+ # defines methods that yield Sequel expression objects representing
55
+ # SQLite json operators and functions.
56
+ #
57
+ # In the method documentation examples, assume that:
58
+ #
59
+ # json_op = Sequel.sqlite_json_op(:json)
60
+ class JSONOp < Sequel::SQL::Wrapper
61
+ GET = ["(".freeze, " ->> ".freeze, ")".freeze].freeze
62
+ private_constant :GET
63
+
64
+ GET_JSON = ["(".freeze, " -> ".freeze, ")".freeze].freeze
65
+ private_constant :GET_JSON
66
+
67
+ # Returns an expression for getting the JSON array element or object field
68
+ # at the specified path as a SQLite value.
69
+ #
70
+ # json_op[1] # (json ->> 1)
71
+ # json_op['a'] # (json ->> 'a')
72
+ # json_op['$.a.b'] # (json ->> '$.a.b')
73
+ # json_op['$[1][2]'] # (json ->> '$[1][2]')
74
+ def [](key)
75
+ json_op(GET, key)
76
+ end
77
+ alias get []
78
+
79
+ # Returns an expression for the length of the JSON array, or the JSON array at
80
+ # the given path.
81
+ #
82
+ # json_op.array_length # json_array_length(json)
83
+ # json_op.array_length('$[1]') # json_array_length(json, '$[1]')
84
+ def array_length(*args)
85
+ Sequel::SQL::NumericExpression.new(:NOOP, function(:array_length, *args))
86
+ end
87
+
88
+ # Returns an expression for a set of information extracted from the top-level
89
+ # members of the JSON array or object, or the top-level members of the JSON array
90
+ # or object at the given path.
91
+ #
92
+ # json_op.each # json_each(json)
93
+ # json_op.each('$.a') # json_each(json, '$.a')
94
+ def each(*args)
95
+ function(:each, *args)
96
+ end
97
+
98
+ # Returns an expression for the JSON array element or object field at the specified
99
+ # path as a SQLite value, but only accept paths as arguments, and allow the use of
100
+ # multiple paths.
101
+ #
102
+ # json_op.extract('$.a') # json_extract(json, '$.a')
103
+ # json_op.extract('$.a', '$.b') # json_extract(json, '$.a', '$.b')
104
+ def extract(*a)
105
+ function(:extract, *a)
106
+ end
107
+
108
+ # Returns an expression for getting the JSON array element or object field at the
109
+ # specified path as a JSON value.
110
+ #
111
+ # json_op.get_json(1) # (json -> 1)
112
+ # json_op.get_json('a') # (json -> 'a')
113
+ # json_op.get_json('$.a.b') # (json -> '$.a.b')
114
+ # json_op.get_json('$[1][2]') # (json -> '$[1][2]')
115
+ def get_json(key)
116
+ self.class.new(json_op(GET_JSON, key))
117
+ end
118
+
119
+ # Returns an expression for creating new entries at the given paths in the JSON array
120
+ # or object, but not overwriting existing entries.
121
+ #
122
+ # json_op.insert('$.a', 1) # json_insert(json, '$.a', 1)
123
+ # json_op.insert('$.a', 1, '$.b', 2) # json_insert(json, '$.a', 1, '$.b', 2)
124
+ def insert(path, value, *args)
125
+ wrapped_function(:insert, path, value, *args)
126
+ end
127
+
128
+ # Returns an expression for a minified version of the JSON.
129
+ #
130
+ # json_op.json # json(json)
131
+ def json
132
+ self.class.new(SQL::Function.new(:json, self))
133
+ end
134
+ alias minify json
135
+
136
+ # Returns an expression for updating the JSON object using the RFC 7396 MergePatch algorithm
137
+ #
138
+ # json_op.patch('{"a": 1, "b": null}') # json_patch(json, '{"a": 1, "b": null}')
139
+ def patch(json_patch)
140
+ wrapped_function(:patch, json_patch)
141
+ end
142
+
143
+ # Returns an expression for removing entries at the given paths from the JSON array or object.
144
+ #
145
+ # json_op.remove('$.a') # json_remove(json, '$.a')
146
+ # json_op.remove('$.a', '$.b') # json_remove(json, '$.a', '$.b')
147
+ def remove(path, *paths)
148
+ wrapped_function(:remove, path, *paths)
149
+ end
150
+
151
+ # Returns an expression for replacing entries at the given paths in the JSON array or object,
152
+ # but not creating new entries.
153
+ #
154
+ # json_op.replace('$.a', 1) # json_replace(json, '$.a', 1)
155
+ # json_op.replace('$.a', 1, '$.b', 2) # json_replace(json, '$.a', 1, '$.b', 2)
156
+ def replace(path, value, *args)
157
+ wrapped_function(:replace, path, value, *args)
158
+ end
159
+
160
+ # Returns an expression for creating or replacing entries at the given paths in the
161
+ # JSON array or object.
162
+ #
163
+ # json_op.set('$.a', 1) # json_set(json, '$.a', 1)
164
+ # json_op.set('$.a', 1, '$.b', 2) # json_set(json, '$.a', 1, '$.b', 2)
165
+ def set(path, value, *args)
166
+ wrapped_function(:set, path, value, *args)
167
+ end
168
+
169
+ # Returns an expression for a set of information extracted from the JSON array or object, or
170
+ # the JSON array or object at the given path.
171
+ #
172
+ # json_op.tree # json_tree(json)
173
+ # json_op.tree('$.a') # json_tree(json, '$.a')
174
+ def tree(*args)
175
+ function(:tree, *args)
176
+ end
177
+
178
+ # Returns an expression for the type of the JSON value or the JSON value at the given path.
179
+ #
180
+ # json_op.type # json_type(json)
181
+ # json_op.type('$[1]') # json_type(json, '$[1]')
182
+ def type(*args)
183
+ Sequel::SQL::StringExpression.new(:NOOP, function(:type, *args))
184
+ end
185
+ alias typeof type
186
+
187
+ # Returns a boolean expression for whether the JSON is valid or not.
188
+ def valid
189
+ Sequel::SQL::BooleanExpression.new(:NOOP, function(:valid))
190
+ end
191
+
192
+ private
193
+
194
+ # Internals of the [], get, get_json methods, using a placeholder literal string.
195
+ def json_op(str, args)
196
+ self.class.new(Sequel::SQL::PlaceholderLiteralString.new(str, [self, args]))
197
+ end
198
+
199
+ # Internals of the methods that return functions prefixed with +json_+.
200
+ def function(name, *args)
201
+ SQL::Function.new("json_#{name}", self, *args)
202
+ end
203
+
204
+ # Internals of the methods that return functions prefixed with +json_+, that
205
+ # return JSON values.
206
+ def wrapped_function(*args)
207
+ self.class.new(function(*args))
208
+ end
209
+ end
210
+
211
+ module JSONOpMethods
212
+ # Wrap the receiver in an JSONOp so you can easily use the SQLite
213
+ # json functions and operators with it.
214
+ def sqlite_json_op
215
+ JSONOp.new(self)
216
+ end
217
+ end
218
+ end
219
+
220
+ module SQL::Builders
221
+ # Return the object wrapped in an SQLite::JSONOp.
222
+ def sqlite_json_op(v)
223
+ case v
224
+ when SQLite::JSONOp
225
+ v
226
+ else
227
+ SQLite::JSONOp.new(v)
228
+ end
229
+ end
230
+ end
231
+
232
+ class SQL::GenericExpression
233
+ include Sequel::SQLite::JSONOpMethods
234
+ end
235
+
236
+ class LiteralString
237
+ include Sequel::SQLite::JSONOpMethods
238
+ end
239
+ end
240
+
241
+ # :nocov:
242
+ if Sequel.core_extensions?
243
+ class Symbol
244
+ include Sequel::SQLite::JSONOpMethods
245
+ end
246
+ end
247
+
248
+ if defined?(Sequel::CoreRefinements)
249
+ module Sequel::CoreRefinements
250
+ refine Symbol do
251
+ send INCLUDE_METH, Sequel::SQLite::JSONOpMethods
252
+ end
253
+ end
254
+ end
255
+ # :nocov:
@@ -29,7 +29,7 @@ module Sequel
29
29
  # end
30
30
  #
31
31
  # +first_track+ is not instance specific, but +last_track+ and +recent_tracks+ are.
32
- # +last_trac+ is because the +num_tracks+ call in the block is calling
32
+ # +last_track+ is because the +num_tracks+ call in the block is calling
33
33
  # <tt>Album#num_tracks</tt>. +recent_tracks+ is because the value will change over
34
34
  # time. This plugin allows you to find these cases, and set the :instance_specific
35
35
  # option appropriately for them:
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 55
9
+ MINOR = 56
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.55.0
4
+ version: 5.56.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-04-01 00:00:00.000000000 Z
11
+ date: 2022-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -200,6 +200,7 @@ extra_rdoc_files:
200
200
  - doc/release_notes/5.53.0.txt
201
201
  - doc/release_notes/5.54.0.txt
202
202
  - doc/release_notes/5.55.0.txt
203
+ - doc/release_notes/5.56.0.txt
203
204
  - doc/release_notes/5.6.0.txt
204
205
  - doc/release_notes/5.7.0.txt
205
206
  - doc/release_notes/5.8.0.txt
@@ -283,6 +284,7 @@ files:
283
284
  - doc/release_notes/5.53.0.txt
284
285
  - doc/release_notes/5.54.0.txt
285
286
  - doc/release_notes/5.55.0.txt
287
+ - doc/release_notes/5.56.0.txt
286
288
  - doc/release_notes/5.6.0.txt
287
289
  - doc/release_notes/5.7.0.txt
288
290
  - doc/release_notes/5.8.0.txt
@@ -453,6 +455,7 @@ files:
453
455
  - lib/sequel/extensions/sql_comments.rb
454
456
  - lib/sequel/extensions/sql_expr.rb
455
457
  - lib/sequel/extensions/sql_log_normalizer.rb
458
+ - lib/sequel/extensions/sqlite_json_ops.rb
456
459
  - lib/sequel/extensions/string_agg.rb
457
460
  - lib/sequel/extensions/string_date_time.rb
458
461
  - lib/sequel/extensions/symbol_aref.rb
@@ -569,13 +572,13 @@ files:
569
572
  - lib/sequel/sql.rb
570
573
  - lib/sequel/timezones.rb
571
574
  - lib/sequel/version.rb
572
- homepage: http://sequel.jeremyevans.net
575
+ homepage: https://sequel.jeremyevans.net
573
576
  licenses:
574
577
  - MIT
575
578
  metadata:
576
579
  bug_tracker_uri: https://github.com/jeremyevans/sequel/issues
577
- changelog_uri: http://sequel.jeremyevans.net/rdoc/files/CHANGELOG.html
578
- documentation_uri: http://sequel.jeremyevans.net/documentation.html
580
+ changelog_uri: https://sequel.jeremyevans.net/rdoc/files/CHANGELOG.html
581
+ documentation_uri: https://sequel.jeremyevans.net/documentation.html
579
582
  mailing_list_uri: https://github.com/jeremyevans/sequel/discussions
580
583
  source_code_uri: https://github.com/jeremyevans/sequel
581
584
  post_install_message: