sequel_core 1.0.8.2 → 1.0.9.1
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 +18 -0
- data/Rakefile +1 -1
- data/lib/sequel_core/adapters/mysql.rb +16 -0
- data/lib/sequel_core/adapters/odbc_mssql.rb +4 -0
- data/lib/sequel_core/adapters/postgres.rb +21 -0
- data/lib/sequel_core/database.rb +6 -0
- data/lib/sequel_core/dataset.rb +15 -1
- data/lib/sequel_core/dataset/sql.rb +2 -2
- data/lib/sequel_core/schema/schema_generator.rb +14 -0
- data/lib/sequel_core/schema/schema_sql.rb +3 -1
- data/spec/adapters/mysql_spec.rb +28 -0
- data/spec/adapters/postgres_spec.rb +40 -0
- data/spec/database_spec.rb +12 -0
- data/spec/dataset_spec.rb +72 -6
- data/spec/schema_generator_spec.rb +3 -1
- data/spec/schema_spec.rb +9 -0
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
=== SVN
|
2
|
+
|
3
|
+
* Changed Dataset#all to accept opts and iteration block.
|
4
|
+
|
5
|
+
=== 1.0.9 (2008-02-10)
|
6
|
+
|
7
|
+
* Implemented Dataset#inspect and Database#inspect (#151).
|
8
|
+
|
9
|
+
* Added full-text searching for odbc_mssql adapter (thanks Joseph Love).
|
10
|
+
|
11
|
+
* Added AlterTableGenerator#add_full_text_index method.
|
12
|
+
|
13
|
+
* Implemented full_text indexing and searching for PostgreSQL adapter (thanks David Lee).
|
14
|
+
|
15
|
+
* Implemented full_text indexing and searching for MySQL adapter (thanks David Lee).
|
16
|
+
|
17
|
+
* Fixed Dataset#insert_sql to work with array subscript references (thanks Jim Morris).
|
18
|
+
|
1
19
|
=== 1.0.8 (2008-02-08)
|
2
20
|
|
3
21
|
* Added support for multiple choices in string matching expressions (#147).
|
data/Rakefile
CHANGED
@@ -9,7 +9,7 @@ include FileUtils
|
|
9
9
|
# Configuration
|
10
10
|
##############################################################################
|
11
11
|
NAME = "sequel_core"
|
12
|
-
VERS = "1.0.
|
12
|
+
VERS = "1.0.9.1"
|
13
13
|
CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
|
14
14
|
RDOC_OPTS = [
|
15
15
|
"--quiet",
|
@@ -173,6 +173,17 @@ module Sequel
|
|
173
173
|
sql
|
174
174
|
end
|
175
175
|
|
176
|
+
def index_definition_sql(table_name, index)
|
177
|
+
index_name = index[:name] || default_index_name(table_name, index[:columns])
|
178
|
+
if index[:full_text]
|
179
|
+
"CREATE FULLTEXT INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
|
180
|
+
elsif index[:unique]
|
181
|
+
"CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
|
182
|
+
else
|
183
|
+
"CREATE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
176
187
|
def transaction
|
177
188
|
@pool.hold do |conn|
|
178
189
|
@transactions ||= []
|
@@ -290,6 +301,11 @@ module Sequel
|
|
290
301
|
sql
|
291
302
|
end
|
292
303
|
alias_method :sql, :select_sql
|
304
|
+
|
305
|
+
def full_text_search(cols, terms, opts = {})
|
306
|
+
mode = opts[:boolean] ? " IN BOOLEAN MODE" : ""
|
307
|
+
filter("MATCH (#{literal(cols)}) AGAINST (#{literal(terms)}#{mode})")
|
308
|
+
end
|
293
309
|
|
294
310
|
# MySQL allows HAVING clause on ungrouped datasets.
|
295
311
|
def having(*cond, &block)
|
@@ -311,6 +311,20 @@ module Sequel
|
|
311
311
|
{:primary_key => true, :type => :serial}
|
312
312
|
end
|
313
313
|
|
314
|
+
def index_definition_sql(table_name, index)
|
315
|
+
index_name = index[:name] || default_index_name(table_name, index[:columns])
|
316
|
+
if index[:full_text]
|
317
|
+
lang = index[:language] ? "#{literal(index[:language])}, " : ""
|
318
|
+
cols = index[:columns].map {|c| literal(c)}.join(" || ")
|
319
|
+
expr = "gin(to_tsvector(#{lang}#{cols}))"
|
320
|
+
"CREATE INDEX #{index_name} ON #{table_name} USING #{expr}"
|
321
|
+
elsif index[:unique]
|
322
|
+
"CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
|
323
|
+
else
|
324
|
+
"CREATE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
314
328
|
def drop_table_sql(name)
|
315
329
|
"DROP TABLE #{name} CASCADE"
|
316
330
|
end
|
@@ -341,6 +355,13 @@ module Sequel
|
|
341
355
|
end
|
342
356
|
end
|
343
357
|
|
358
|
+
def full_text_search(cols, terms, opts = {})
|
359
|
+
lang = opts[:language] ? "#{literal(opts[:language])}, " : ""
|
360
|
+
cols = cols.is_a?(Array) ? cols.map {|c| literal(c)}.join(" || ") : literal(cols)
|
361
|
+
terms = terms.is_a?(Array) ? literal(terms.join(" | ")) : literal(terms)
|
362
|
+
filter("to_tsvector(#{lang}#{cols}) @@ to_tsquery(#{lang}#{terms})")
|
363
|
+
end
|
364
|
+
|
344
365
|
FOR_UPDATE = ' FOR UPDATE'.freeze
|
345
366
|
FOR_SHARE = ' FOR SHARE'.freeze
|
346
367
|
|
data/lib/sequel_core/database.rb
CHANGED
@@ -337,6 +337,12 @@ module Sequel
|
|
337
337
|
end
|
338
338
|
end
|
339
339
|
end
|
340
|
+
|
341
|
+
# Returns a string representation of the database object including the
|
342
|
+
# class name and the connection URI.
|
343
|
+
def inspect
|
344
|
+
'#<%s: %s>' % [self.class.to_s, uri.inspect]
|
345
|
+
end
|
340
346
|
|
341
347
|
@@adapters = Hash.new
|
342
348
|
|
data/lib/sequel_core/dataset.rb
CHANGED
@@ -72,8 +72,16 @@ module Sequel
|
|
72
72
|
attr_reader :db
|
73
73
|
attr_accessor :opts
|
74
74
|
|
75
|
-
alias_method :all, :to_a
|
76
75
|
alias_method :size, :count
|
76
|
+
|
77
|
+
# Returns an array with all records in the dataset. If a block is given,
|
78
|
+
# the array is iterated over.
|
79
|
+
def all(opts = nil, &block)
|
80
|
+
a = []
|
81
|
+
each(opts) {|r| a << r}
|
82
|
+
a.each(&block) if block
|
83
|
+
a
|
84
|
+
end
|
77
85
|
|
78
86
|
# Constructs a new instance of a dataset with a database instance, initial
|
79
87
|
# options and an optional record class. Datasets are usually constructed by
|
@@ -421,6 +429,12 @@ module Sequel
|
|
421
429
|
def self.inherited(c) #:nodoc:
|
422
430
|
@@dataset_classes << c
|
423
431
|
end
|
432
|
+
|
433
|
+
# Returns a string representation of the dataset including the class name
|
434
|
+
# and the corresponding SQL select statement.
|
435
|
+
def inspect
|
436
|
+
'#<%s: %s>' % [self.class.to_s, sql.inspect]
|
437
|
+
end
|
424
438
|
end
|
425
439
|
end
|
426
440
|
|
@@ -496,7 +496,7 @@ module Sequel
|
|
496
496
|
if values.empty?
|
497
497
|
"INSERT INTO #{@opts[:from]} DEFAULT VALUES"
|
498
498
|
elsif values.keys
|
499
|
-
fl = values.keys.map {|f| literal(f.to_sym)}
|
499
|
+
fl = values.keys.map {|f| literal(f.is_a?(String) ? f.to_sym : f)}
|
500
500
|
vl = @transform ? transform_save(values.values) : values.values
|
501
501
|
vl.map! {|v| literal(v)}
|
502
502
|
"INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
|
@@ -509,7 +509,7 @@ module Sequel
|
|
509
509
|
"INSERT INTO #{@opts[:from]} DEFAULT VALUES"
|
510
510
|
else
|
511
511
|
fl, vl = [], []
|
512
|
-
values.each {|k, v| fl << literal(k.to_sym); vl << literal(v)}
|
512
|
+
values.each {|k, v| fl << literal(k.is_a?(String) ? k.to_sym : k); vl << literal(v)}
|
513
513
|
"INSERT INTO #{@opts[:from]} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
|
514
514
|
end
|
515
515
|
when Dataset
|
@@ -58,6 +58,11 @@ module Sequel
|
|
58
58
|
@indexes << {:columns => columns}.merge(opts)
|
59
59
|
end
|
60
60
|
|
61
|
+
def full_text_index(columns, opts = {})
|
62
|
+
columns = [columns] unless columns.is_a?(Array)
|
63
|
+
@indexes << {:columns => columns, :full_text => true}.merge(opts)
|
64
|
+
end
|
65
|
+
|
61
66
|
def unique(columns, opts = {})
|
62
67
|
index(columns, {:unique => true}.merge(opts))
|
63
68
|
end
|
@@ -126,6 +131,15 @@ module Sequel
|
|
126
131
|
}.merge(opts)
|
127
132
|
end
|
128
133
|
|
134
|
+
def add_full_text_index(columns, opts = {})
|
135
|
+
columns = [columns] unless columns.is_a?(Array)
|
136
|
+
@operations << { \
|
137
|
+
:op => :add_index, \
|
138
|
+
:columns => columns, \
|
139
|
+
:full_text => true \
|
140
|
+
}.merge(opts)
|
141
|
+
end
|
142
|
+
|
129
143
|
def drop_index(columns)
|
130
144
|
columns = [columns] unless columns.is_a?(Array)
|
131
145
|
@operations << { \
|
@@ -90,7 +90,9 @@ module Sequel
|
|
90
90
|
|
91
91
|
def index_definition_sql(table_name, index)
|
92
92
|
index_name = index[:name] || default_index_name(table_name, index[:columns])
|
93
|
-
if index[:
|
93
|
+
if index[:full_text]
|
94
|
+
raise Error, "Full-text indexes are not supported for this database"
|
95
|
+
elsif index[:unique]
|
94
96
|
"CREATE UNIQUE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
|
95
97
|
else
|
96
98
|
"CREATE INDEX #{index_name} ON #{table_name} (#{literal(index[:columns])})"
|
data/spec/adapters/mysql_spec.rb
CHANGED
@@ -413,3 +413,31 @@ context "A grouped MySQL dataset" do
|
|
413
413
|
ds.count.should == 1
|
414
414
|
end
|
415
415
|
end
|
416
|
+
|
417
|
+
context "A MySQL database" do
|
418
|
+
setup do
|
419
|
+
end
|
420
|
+
|
421
|
+
specify "should support fulltext indexes" do
|
422
|
+
g = Sequel::Schema::Generator.new(MYSQL_DB) do
|
423
|
+
text :title
|
424
|
+
text :body
|
425
|
+
full_text_index [:title, :body]
|
426
|
+
end
|
427
|
+
MYSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
|
428
|
+
"CREATE TABLE posts (`title` text, `body` text)",
|
429
|
+
"CREATE FULLTEXT INDEX posts_title_body_index ON posts (`title`, `body`)"
|
430
|
+
]
|
431
|
+
end
|
432
|
+
|
433
|
+
specify "should support full_text_search" do
|
434
|
+
MYSQL_DB[:posts].full_text_search(:title, 'ruby').sql.should ==
|
435
|
+
"SELECT * FROM posts WHERE (MATCH (`title`) AGAINST ('ruby'))"
|
436
|
+
|
437
|
+
MYSQL_DB[:posts].full_text_search([:title, :body], ['ruby', 'sequel']).sql.should ==
|
438
|
+
"SELECT * FROM posts WHERE (MATCH (`title`, `body`) AGAINST ('ruby', 'sequel'))"
|
439
|
+
|
440
|
+
MYSQL_DB[:posts].full_text_search(:title, '+ruby -rails', :boolean => true).sql.should ==
|
441
|
+
"SELECT * FROM posts WHERE (MATCH (`title`) AGAINST ('+ruby -rails' IN BOOLEAN MODE))"
|
442
|
+
end
|
443
|
+
end
|
@@ -245,3 +245,43 @@ context "A PostgreSQL database" do
|
|
245
245
|
@db[:test2].first[:xyz].should == 57
|
246
246
|
end
|
247
247
|
end
|
248
|
+
|
249
|
+
context "A PostgreSSQL database" do
|
250
|
+
setup do
|
251
|
+
end
|
252
|
+
|
253
|
+
specify "should support fulltext indexes" do
|
254
|
+
g = Sequel::Schema::Generator.new(PGSQL_DB) do
|
255
|
+
text :title
|
256
|
+
text :body
|
257
|
+
full_text_index [:title, :body]
|
258
|
+
end
|
259
|
+
PGSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
|
260
|
+
"CREATE TABLE posts (\"title\" text, \"body\" text)",
|
261
|
+
"CREATE INDEX posts_title_body_index ON posts USING gin(to_tsvector(\"title\" || \"body\"))"
|
262
|
+
]
|
263
|
+
end
|
264
|
+
|
265
|
+
specify "should support fulltext indexes with a specific language" do
|
266
|
+
g = Sequel::Schema::Generator.new(PGSQL_DB) do
|
267
|
+
text :title
|
268
|
+
text :body
|
269
|
+
full_text_index [:title, :body], :language => 'french'
|
270
|
+
end
|
271
|
+
PGSQL_DB.create_table_sql_list(:posts, *g.create_info).should == [
|
272
|
+
"CREATE TABLE posts (\"title\" text, \"body\" text)",
|
273
|
+
"CREATE INDEX posts_title_body_index ON posts USING gin(to_tsvector('french', \"title\" || \"body\"))"
|
274
|
+
]
|
275
|
+
end
|
276
|
+
|
277
|
+
specify "should support full_text_search" do
|
278
|
+
PGSQL_DB[:posts].full_text_search(:title, 'ruby').sql.should ==
|
279
|
+
"SELECT * FROM posts WHERE (to_tsvector(\"title\") @@ to_tsquery('ruby'))"
|
280
|
+
|
281
|
+
PGSQL_DB[:posts].full_text_search([:title, :body], ['ruby', 'sequel']).sql.should ==
|
282
|
+
"SELECT * FROM posts WHERE (to_tsvector(\"title\" || \"body\") @@ to_tsquery('ruby | sequel'))"
|
283
|
+
|
284
|
+
PGSQL_DB[:posts].full_text_search(:title, 'ruby', :language => 'french').sql.should ==
|
285
|
+
"SELECT * FROM posts WHERE (to_tsvector('french', \"title\") @@ to_tsquery('french', 'ruby'))"
|
286
|
+
end
|
287
|
+
end
|
data/spec/database_spec.rb
CHANGED
@@ -835,3 +835,15 @@ context "Database.connect" do
|
|
835
835
|
db.opts[:host].should == 'alfonso'
|
836
836
|
end
|
837
837
|
end
|
838
|
+
|
839
|
+
context "Database#inspect" do
|
840
|
+
setup do
|
841
|
+
@db = DummyDatabase.new
|
842
|
+
|
843
|
+
@db.meta_def(:uri) {'blah://blahblah/blah'}
|
844
|
+
end
|
845
|
+
|
846
|
+
specify "should include the class name and the connection url" do
|
847
|
+
@db.inspect.should == '#<DummyDatabase: "blah://blahblah/blah">'
|
848
|
+
end
|
849
|
+
end
|
data/spec/dataset_spec.rb
CHANGED
@@ -18,21 +18,21 @@ context "Dataset" do
|
|
18
18
|
d.opts.should == {}
|
19
19
|
end
|
20
20
|
|
21
|
-
specify "should provide clone for chainability
|
22
|
-
d1 = @dataset.clone(:from => :test)
|
21
|
+
specify "should provide clone for chainability" do
|
22
|
+
d1 = @dataset.clone(:from => [:test])
|
23
23
|
d1.class.should == @dataset.class
|
24
24
|
d1.should_not == @dataset
|
25
25
|
d1.db.should be(@dataset.db)
|
26
|
-
d1.opts[:from].should == :test
|
26
|
+
d1.opts[:from].should == [:test]
|
27
27
|
@dataset.opts[:from].should be_nil
|
28
28
|
|
29
|
-
d2 = d1.clone(:order => :name)
|
29
|
+
d2 = d1.clone(:order => [:name])
|
30
30
|
d2.class.should == @dataset.class
|
31
31
|
d2.should_not == d1
|
32
32
|
d2.should_not == @dataset
|
33
33
|
d2.db.should be(@dataset.db)
|
34
|
-
d2.opts[:from].should == :test
|
35
|
-
d2.opts[:order].should == :name
|
34
|
+
d2.opts[:from].should == [:test]
|
35
|
+
d2.opts[:order].should == [:name]
|
36
36
|
d1.opts[:order].should be_nil
|
37
37
|
end
|
38
38
|
|
@@ -2539,6 +2539,24 @@ context "Dataset#update_sql" do
|
|
2539
2539
|
end
|
2540
2540
|
end
|
2541
2541
|
|
2542
|
+
context "Dataset#insert_sql" do
|
2543
|
+
setup do
|
2544
|
+
@ds = Sequel::Dataset.new(nil).from(:items)
|
2545
|
+
end
|
2546
|
+
|
2547
|
+
specify "should accept hash with symbol keys" do
|
2548
|
+
@ds.insert_sql(:c => 'd').should == "INSERT INTO items (c) VALUES ('d')"
|
2549
|
+
end
|
2550
|
+
|
2551
|
+
specify "should accept hash with string keys" do
|
2552
|
+
@ds.insert_sql('c' => 'd').should == "INSERT INTO items (c) VALUES ('d')"
|
2553
|
+
end
|
2554
|
+
|
2555
|
+
specify "should accept array subscript references" do
|
2556
|
+
@ds.insert_sql((:day|1) => 'd').should == "INSERT INTO items (day[1]) VALUES ('d')"
|
2557
|
+
end
|
2558
|
+
end
|
2559
|
+
|
2542
2560
|
class DummyMummyDataset < Sequel::Dataset
|
2543
2561
|
def first
|
2544
2562
|
raise if @opts[:from] == [:a]
|
@@ -2592,3 +2610,51 @@ context "Dataset#table_exists?" do
|
|
2592
2610
|
end
|
2593
2611
|
end
|
2594
2612
|
|
2613
|
+
context "Dataset#inspect" do
|
2614
|
+
setup do
|
2615
|
+
@ds = Sequel::Dataset.new(nil).from(:blah)
|
2616
|
+
end
|
2617
|
+
|
2618
|
+
specify "should include the class name and the corresponding SQL statement" do
|
2619
|
+
@ds.inspect.should == '#<%s: %s>' % [@ds.class.to_s, @ds.sql.inspect]
|
2620
|
+
end
|
2621
|
+
end
|
2622
|
+
|
2623
|
+
context "Dataset#all" do
|
2624
|
+
setup do
|
2625
|
+
@c = Class.new(Sequel::Dataset) do
|
2626
|
+
def fetch_rows(sql, &block)
|
2627
|
+
block.call({:x => 1, :y => 2})
|
2628
|
+
block.call({:x => 3, :y => 4})
|
2629
|
+
block.call(sql)
|
2630
|
+
end
|
2631
|
+
end
|
2632
|
+
@dataset = @c.new(nil).from(:items)
|
2633
|
+
end
|
2634
|
+
|
2635
|
+
specify "should return an array with all records" do
|
2636
|
+
@dataset.all.should == [
|
2637
|
+
{:x => 1, :y => 2},
|
2638
|
+
{:x => 3, :y => 4},
|
2639
|
+
"SELECT * FROM items"
|
2640
|
+
]
|
2641
|
+
end
|
2642
|
+
|
2643
|
+
specify "should accept options and pass them to #each" do
|
2644
|
+
@dataset.all(:limit => 33).should == [
|
2645
|
+
{:x => 1, :y => 2},
|
2646
|
+
{:x => 3, :y => 4},
|
2647
|
+
"SELECT * FROM items LIMIT 33"
|
2648
|
+
]
|
2649
|
+
end
|
2650
|
+
|
2651
|
+
specify "should iterate over the array if a block is given" do
|
2652
|
+
a = []
|
2653
|
+
|
2654
|
+
@dataset.all do |r|
|
2655
|
+
a << (r.is_a?(Hash) ? r[:x] : r)
|
2656
|
+
end
|
2657
|
+
|
2658
|
+
a.should == [1, 3, "SELECT * FROM items"]
|
2659
|
+
end
|
2660
|
+
end
|
@@ -81,6 +81,7 @@ describe Sequel::Schema::AlterTableGenerator do
|
|
81
81
|
set_column_default :eee, 1
|
82
82
|
add_index [:fff, :ggg]
|
83
83
|
drop_index :hhh
|
84
|
+
add_full_text_index :blah
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
@@ -92,7 +93,8 @@ describe Sequel::Schema::AlterTableGenerator do
|
|
92
93
|
{:op => :set_column_type, :name => :ddd, :type => :float},
|
93
94
|
{:op => :set_column_default, :name => :eee, :default => 1},
|
94
95
|
{:op => :add_index, :columns => [:fff, :ggg]},
|
95
|
-
{:op => :drop_index, :columns => [:hhh]}
|
96
|
+
{:op => :drop_index, :columns => [:hhh]},
|
97
|
+
{:op => :add_index, :columns => [:blah], :full_text => true}
|
96
98
|
]
|
97
99
|
end
|
98
100
|
end
|
data/spec/schema_spec.rb
CHANGED
@@ -180,6 +180,15 @@ context "DB#create_table" do
|
|
180
180
|
@db.sqls.should == ["CREATE TABLE cats (name text)", "CREATE UNIQUE INDEX cats_name_index ON cats (name)"]
|
181
181
|
end
|
182
182
|
|
183
|
+
specify "should raise on full-text index definitions" do
|
184
|
+
proc {
|
185
|
+
@db.create_table(:cats) do
|
186
|
+
text :name
|
187
|
+
full_text_index :name
|
188
|
+
end
|
189
|
+
}.should raise_error(Sequel::Error)
|
190
|
+
end
|
191
|
+
|
183
192
|
specify "should accept multiple index definitions" do
|
184
193
|
@db.create_table(:cats) do
|
185
194
|
integer :id
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel_core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-02-
|
12
|
+
date: 2008-02-11 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|