sequel_core 1.0.8.2 → 1.0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|