rasti-db 1.5.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +52 -19
  3. data/lib/rasti/db.rb +11 -2
  4. data/lib/rasti/db/collection.rb +67 -36
  5. data/lib/rasti/db/computed_attribute.rb +22 -0
  6. data/lib/rasti/db/data_source.rb +18 -0
  7. data/lib/rasti/db/environment.rb +32 -0
  8. data/lib/rasti/db/nql/filter_condition_strategies/base.rb +17 -0
  9. data/lib/rasti/db/nql/filter_condition_strategies/postgres.rb +21 -0
  10. data/lib/rasti/db/nql/filter_condition_strategies/sqlite.rb +21 -0
  11. data/lib/rasti/db/nql/filter_condition_strategies/types/generic.rb +49 -0
  12. data/lib/rasti/db/nql/filter_condition_strategies/types/pg_array.rb +32 -0
  13. data/lib/rasti/db/nql/filter_condition_strategies/types/sqlite_array.rb +34 -0
  14. data/lib/rasti/db/nql/filter_condition_strategies/unsupported_type_comparison.rb +22 -0
  15. data/lib/rasti/db/nql/nodes/array_content.rb +21 -0
  16. data/lib/rasti/db/nql/nodes/attribute.rb +37 -0
  17. data/lib/rasti/db/nql/nodes/binary_node.rb +4 -0
  18. data/lib/rasti/db/nql/nodes/comparisons/base.rb +15 -1
  19. data/lib/rasti/db/nql/nodes/comparisons/equal.rb +0 -4
  20. data/lib/rasti/db/nql/nodes/comparisons/greater_than.rb +0 -4
  21. data/lib/rasti/db/nql/nodes/comparisons/greater_than_or_equal.rb +0 -4
  22. data/lib/rasti/db/nql/nodes/comparisons/include.rb +0 -4
  23. data/lib/rasti/db/nql/nodes/comparisons/less_than.rb +0 -4
  24. data/lib/rasti/db/nql/nodes/comparisons/less_than_or_equal.rb +0 -4
  25. data/lib/rasti/db/nql/nodes/comparisons/like.rb +0 -4
  26. data/lib/rasti/db/nql/nodes/comparisons/not_equal.rb +0 -4
  27. data/lib/rasti/db/nql/nodes/comparisons/not_include.rb +0 -4
  28. data/lib/rasti/db/nql/nodes/conjunction.rb +2 -2
  29. data/lib/rasti/db/nql/nodes/constants/array.rb +17 -0
  30. data/lib/rasti/db/nql/nodes/constants/base.rb +17 -0
  31. data/lib/rasti/db/nql/nodes/constants/false.rb +1 -1
  32. data/lib/rasti/db/nql/nodes/constants/float.rb +1 -1
  33. data/lib/rasti/db/nql/nodes/constants/integer.rb +1 -1
  34. data/lib/rasti/db/nql/nodes/constants/literal_string.rb +1 -1
  35. data/lib/rasti/db/nql/nodes/constants/string.rb +1 -1
  36. data/lib/rasti/db/nql/nodes/constants/time.rb +1 -1
  37. data/lib/rasti/db/nql/nodes/constants/true.rb +1 -1
  38. data/lib/rasti/db/nql/nodes/disjunction.rb +2 -2
  39. data/lib/rasti/db/nql/nodes/parenthesis_sentence.rb +6 -2
  40. data/lib/rasti/db/nql/nodes/sentence.rb +6 -2
  41. data/lib/rasti/db/nql/syntax.rb +262 -44
  42. data/lib/rasti/db/nql/syntax.treetop +27 -14
  43. data/lib/rasti/db/query.rb +55 -23
  44. data/lib/rasti/db/relations/base.rb +22 -8
  45. data/lib/rasti/db/relations/graph.rb +10 -16
  46. data/lib/rasti/db/relations/many_to_many.rb +57 -23
  47. data/lib/rasti/db/relations/many_to_one.rb +9 -7
  48. data/lib/rasti/db/relations/one_to_many.rb +21 -13
  49. data/lib/rasti/db/type_converters/sqlite.rb +62 -0
  50. data/lib/rasti/db/type_converters/sqlite_types/array.rb +34 -0
  51. data/lib/rasti/db/version.rb +1 -1
  52. data/rasti-db.gemspec +1 -0
  53. data/spec/collection_spec.rb +210 -50
  54. data/spec/computed_attribute_spec.rb +32 -0
  55. data/spec/minitest_helper.rb +77 -15
  56. data/spec/model_spec.rb +4 -2
  57. data/spec/nql/computed_attributes_spec.rb +29 -0
  58. data/spec/nql/filter_condition_spec.rb +23 -4
  59. data/spec/nql/filter_condition_strategies_spec.rb +112 -0
  60. data/spec/nql/syntax_parser_spec.rb +36 -5
  61. data/spec/query_spec.rb +340 -54
  62. data/spec/relations_spec.rb +27 -7
  63. data/spec/type_converters/sqlite_spec.rb +66 -0
  64. metadata +40 -4
  65. data/lib/rasti/db/helpers.rb +0 -16
  66. data/lib/rasti/db/nql/nodes/field.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b801c94dd51c4ee11f624cb542d784a42e445622e0090c42cb9d47322b87e855
4
- data.tar.gz: 518c58daf408e477f80cdac52837a0e2d3256938352c888a73e369f3eca5b7a2
3
+ metadata.gz: 057df50ee7debeb86b2ad63d4dffc965df8941e0f19ac4d4f96fe97ca319c314
4
+ data.tar.gz: c0cda671e2ea8f748b59c34108440edc7e016702f4d5832efac995a650005e62
5
5
  SHA512:
6
- metadata.gz: ae1452c9b77c6d50f9aa8b05b85f9970c801369e7e3a67a7a88436762fbacea4191390e114be066458d7bc2dc563f834cda93a364185354c74267db86798a889
7
- data.tar.gz: 2707b44943ea63f17cd0f613c4fa08bdfc0663e8fde17f604c8c6ce9ba8f1fff23b4fa1641dfe42f77810a55fda6794491f5e8d8cb756c4fc19adb1cbb8cba76
6
+ metadata.gz: d31034d885d8791bb04a2417372d56226c51b4fe8c54279d294148b22c68a7744f12887a948692f12e0b24c1a4abf8868b3c9250948ffb516e2c2515b42ea0aa
7
+ data.tar.gz: b75bc69a65eb10032f510ff5cb99cd511b69bc354c6bc3bc836e23612a0a21f9cc0a01849b752d58b1e86c0b9387adcb665ad609198495120a1da0c6fa2418a7
data/README.md CHANGED
@@ -28,49 +28,62 @@ Or install it yourself as:
28
28
  ### Database connection
29
29
 
30
30
  ```ruby
31
- DB = Sequel.connect ...
31
+ DB_1 = Sequel.connect ...
32
+ DB_2 = Sequel.connect ...
32
33
  ```
33
34
 
34
35
  ### Database schema
35
36
 
36
37
  ```ruby
37
- DB.create_table :users do
38
+ DB_1.create_table :users do
38
39
  primary_key :id
39
40
  String :name, null: false, unique: true
40
41
  end
41
42
 
42
- DB.create_table :posts do
43
+ DB_1.create_table :posts do
43
44
  primary_key :id
44
45
  String :title, null: false, unique: true
45
46
  String :body, null: false
47
+ Integer :language_id, null: false, index: true
46
48
  foreign_key :user_id, :users, null: false, index: true
47
49
  end
48
50
 
49
- DB.create_table :comments do
51
+ DB_1.create_table :comments do
50
52
  primary_key :id
51
53
  String :text, null: false
52
54
  foreign_key :user_id, :users, null: false, index: true
53
55
  foreign_key :post_id, :posts, null: false, index: true
54
56
  end
55
57
 
56
- DB.create_table :categories do
58
+ DB_1.create_table :categories do
57
59
  primary_key :id
58
60
  String :name, null: false, unique: true
59
61
  end
60
62
 
61
- DB.create_table :categories_posts do
63
+ DB_1.create_table :categories_posts do
62
64
  foreign_key :category_id, :categories, null: false, index: true
63
65
  foreign_key :post_id, :posts, null: false, index: true
64
66
  primary_key [:category_id, :post_id]
65
67
  end
66
68
 
67
- DB.create_table :people do
69
+ DB_1.create_table :people do
68
70
  String :document_number, null: false, primary_key: true
69
71
  String :first_name, null: false
70
72
  String :last_name, null: false
71
73
  Date :birth_date, null: false
72
74
  foreign_key :user_id, :users, null: false, unique: true
73
75
  end
76
+
77
+ DB_1.create_table :languages_people do
78
+ Integer :language_id, null: false, index: true
79
+ foreign_key :document_number, :people, type: String, null: false, index: true
80
+ primary_key [:language_id, :document_number]
81
+ end
82
+
83
+ DB_2.create_table :languages do
84
+ primary_key :id
85
+ String :name, null: false, unique: true
86
+ end
74
87
  ```
75
88
 
76
89
  ### Models
@@ -80,7 +93,8 @@ User = Rasti::DB::Model[:id, :name, :posts, :comments, :person]
80
93
  Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :categories]
81
94
  Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
82
95
  Category = Rasti::DB::Model[:id, :name, :posts]
83
- Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
96
+ Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :full_name, :birth_date, :user_id, :user]
97
+ Language = Rasti::DB::Model[:id, :name, :people]
84
98
  ```
85
99
 
86
100
  ### Collections
@@ -97,16 +111,16 @@ class Posts < Rasti::DB::Collection
97
111
  many_to_many :categories
98
112
  one_to_many :comments
99
113
 
100
- query :created_by do |user_id|
114
+ query :created_by do |user_id|
101
115
  where user_id: user_id
102
116
  end
103
-
117
+
104
118
  query :entitled, -> (title) { where title: title }
105
119
 
106
120
  query :commented_by do |user_id|
107
121
  chainable do
108
- dataset.join(with_schema(:comments), post_id: :id)
109
- .where(with_schema(:comments, :user_id) => user_id)
122
+ dataset.join(qualify(:comments), post_id: :id)
123
+ .where(Sequel[:comments][:user_id] => user_id)
110
124
  .select_all(:posts)
111
125
  .distinct
112
126
  end
@@ -129,19 +143,34 @@ class People < Rasti::DB::Collection
129
143
  set_model Person
130
144
 
131
145
  many_to_one :user
146
+ many_to_many :languages
147
+
148
+ computed_attribute :full_name do
149
+ Rasti::DB::ComputedAttribute.new Sequel.join([:first_name, ' ', :last_name])
150
+ end
132
151
  end
133
152
 
134
- users = Users.new DB
135
- posts = Posts.new DB
136
- comments = Comments.new DB
137
- categories = Categories.new DB
138
- people = People.new DB
153
+ class Languages < Rasti::DB::Collection
154
+ set_data_source_name :other
155
+
156
+ one_to_many :posts
157
+ many_to_many :people, collection: People, relation_data_source_name: :default
158
+ end
159
+
160
+ environment = Rasti::DB::Environment.new default: Rasti::DB::DataSource.new(DB_1),
161
+ other: Rasti::DB::DataSource.new(DB_2, 'custom_schema')
162
+
163
+ users = Users.new environment
164
+ posts = Posts.new environment
165
+ comments = Comments.new environment
166
+ categories = Categories.new environment
167
+ people = People.new environment
139
168
  ```
140
169
 
141
170
  ### Persistence
142
171
 
143
172
  ```ruby
144
- DB.transaction do
173
+ DB_1.transaction do
145
174
  id = users.insert name: 'User 1'
146
175
  users.update id, name: 'User updated'
147
176
  users.delete id
@@ -174,7 +203,7 @@ posts.created_by(1) # => [Post, ...]
174
203
  posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
175
204
  posts.with_categories([1,2]) # => [Post, ...]
176
205
 
177
- posts.graph(:user, :categories, 'comments.user') # => [Post(User, [Categories, ...], [Comments(User)]), ...]
206
+ posts.graph(:user, :language, :categories, 'comments.user') # => [Post(User, Language, [Categories, ...], [Comments(User)]), ...]
178
207
 
179
208
  posts.join(:user).where(name: 'User 4') # => [Post, ...]
180
209
 
@@ -185,6 +214,10 @@ posts.all_attributes # => [Post, ...]
185
214
  posts.graph('user.person').select_graph_attributes(user: [:id], 'user.person': [:last_name, :user_id]) # => [Post, ...]
186
215
  posts.graph('user.person').exclude_graph_attributes(user: [:name], 'user.person': [:first_name, :last_name]) # => [Post, ...]
187
216
  posts.graph('user.person').all_graph_attributes('user.person') # => [Post, ...]
217
+
218
+ posts.each { |post| do_something post } # Iterate posts loading all at first
219
+ posts.each(batch_size: 1000) { |post| do_something post } # Iterate posts loading in batches
220
+ posts.each_batch(size: 1000) { |posts| do_something posts } # Iterate batches of posts
188
221
  ```
189
222
  ### Natural Query Language
190
223
 
@@ -6,21 +6,25 @@ require 'treetop'
6
6
  require 'hierarchical_graph'
7
7
  require 'class_config'
8
8
  require 'hash_ext'
9
+ require 'inflecto'
9
10
  require 'multi_require'
10
11
 
11
12
  module Rasti
12
13
  module DB
13
-
14
+
14
15
  extend MultiRequire
15
16
  extend ClassConfig
16
17
 
17
- require_relative 'db/helpers'
18
18
  require_relative 'db/query'
19
19
  require_relative_pattern 'db/relations/*'
20
20
  require_relative_pattern 'db/type_converters/postgres_types/*'
21
+ require_relative_pattern 'db/type_converters/sqlite_types/*'
22
+ require_relative 'db/nql/nodes/constants/base'
23
+ require_relative_pattern 'db/nql/filter_condition_strategies/types/*'
21
24
  require_relative_pattern 'db/**/*'
22
25
 
23
26
  attr_config :type_converters, []
27
+ attr_config :nql_filter_condition_strategy, nil
24
28
 
25
29
  def self.to_db(db, collection_name, attribute_name, value)
26
30
  type_converters.inject(value) do |result, type_converter|
@@ -34,5 +38,10 @@ module Rasti
34
38
  end
35
39
  end
36
40
 
41
+ def self.nql_filter_condition_for(comparison_name, identifier, argument)
42
+ raise 'Undefined Rasti::DB.nql_filter_condition_strategy' unless nql_filter_condition_strategy
43
+ nql_filter_condition_strategy.filter_condition_for comparison_name, identifier, argument
44
+ end
45
+
37
46
  end
38
47
  end
@@ -4,8 +4,6 @@ module Rasti
4
4
 
5
5
  QUERY_METHODS = Query.public_instance_methods - Object.public_instance_methods
6
6
 
7
- include Helpers::WithSchema
8
-
9
7
  class << self
10
8
 
11
9
  extend Sequel::Inflections
@@ -16,7 +14,7 @@ module Rasti
16
14
  end
17
15
 
18
16
  def collection_attributes
19
- @collection_attributes ||= model.attributes - relations.keys
17
+ @collection_attributes ||= model.attributes - relations.keys - computed_attributes.keys
20
18
  end
21
19
 
22
20
  def primary_key
@@ -37,12 +35,20 @@ module Rasti
37
35
  end
38
36
  end
39
37
 
38
+ def data_source_name
39
+ @data_source_name ||= superclass.respond_to?(:data_source_name) ? superclass.data_source_name : :default
40
+ end
41
+
40
42
  def relations
41
- @relations ||= {}
43
+ @relations ||= Hash::Indifferent.new
42
44
  end
43
45
 
44
46
  def queries
45
- @queries ||= {}
47
+ @queries ||= Hash::Indifferent.new
48
+ end
49
+
50
+ def computed_attributes
51
+ @computed_attributes ||= Hash::Indifferent.new
46
52
  end
47
53
 
48
54
  private
@@ -63,11 +69,17 @@ module Rasti
63
69
  @model = model
64
70
  end
65
71
 
72
+ def set_data_source_name(name)
73
+ @data_source_name = name.to_sym
74
+ end
75
+
66
76
  [Relations::OneToMany, Relations::ManyToOne, Relations::ManyToMany, Relations::OneToOne].each do |relation_class|
67
77
  define_method underscore(demodulize(relation_class.name)) do |name, options={}|
68
78
  relations[name] = relation_class.new name, self, options
69
79
 
70
- query "with_#{pluralize(name)}".to_sym do |primary_keys|
80
+ query_name = relations[name].to_many? ? name : pluralize(name)
81
+
82
+ query "with_#{query_name}" do |primary_keys|
71
83
  with_related name, primary_keys
72
84
  end
73
85
  end
@@ -83,13 +95,15 @@ module Rasti
83
95
  end
84
96
  end
85
97
 
86
- end
98
+ def computed_attribute(name, &block)
99
+ raise "Computed Attribute #{name} already exists" if computed_attributes.key? name
100
+ computed_attributes[name] = block.call
101
+ end
87
102
 
88
- attr_reader :db, :schema
103
+ end
89
104
 
90
- def initialize(db, schema=nil)
91
- @db = db
92
- @schema = schema ? schema.to_sym : nil
105
+ def initialize(environment)
106
+ @environment = environment
93
107
  end
94
108
 
95
109
  QUERY_METHODS.each do |method|
@@ -98,12 +112,8 @@ module Rasti
98
112
  end
99
113
  end
100
114
 
101
- def dataset
102
- db[qualified_collection_name]
103
- end
104
-
105
115
  def insert(attributes)
106
- db.transaction do
116
+ data_source.db.transaction do
107
117
  db_attributes = transform_attributes_to_db attributes
108
118
  collection_attributes, relations_primary_keys = split_related_attributes db_attributes
109
119
  primary_key = dataset.insert collection_attributes
@@ -126,7 +136,7 @@ module Rasti
126
136
  end
127
137
 
128
138
  def update(primary_key, attributes)
129
- db.transaction do
139
+ data_source.db.transaction do
130
140
  db_attributes = transform_attributes_to_db attributes
131
141
  collection_attributes, relations_primary_keys = split_related_attributes db_attributes
132
142
  dataset.where(self.class.primary_key => primary_key).update(collection_attributes) unless collection_attributes.empty?
@@ -152,7 +162,7 @@ module Rasti
152
162
  end
153
163
 
154
164
  def delete_relations(primary_key, relations)
155
- db.transaction do
165
+ data_source.db.transaction do
156
166
  relations.each do |relation_name, relation_primary_keys|
157
167
  relation = self.class.relations[relation_name]
158
168
  delete_relation_table relation, primary_key, relation_primary_keys
@@ -162,7 +172,7 @@ module Rasti
162
172
  end
163
173
 
164
174
  def delete_cascade(*primary_keys)
165
- db.transaction do
175
+ data_source.db.transaction do
166
176
  delete_cascade_relations primary_keys
167
177
  bulk_delete { |q| q.where self.class.primary_key => primary_keys }
168
178
  end
@@ -187,33 +197,48 @@ module Rasti
187
197
 
188
198
  private
189
199
 
190
- def transform_attributes_to_db(attributes)
191
- attributes.each_with_object({}) do |(attribute_name, value), result|
192
- transformed_value = Rasti::DB.to_db db, qualified_collection_name, attribute_name, value
193
- result[attribute_name] = transformed_value
194
- end
200
+ attr_reader :environment
201
+
202
+ def data_source
203
+ @data_source ||= environment.data_source_of self.class
204
+ end
205
+
206
+ def dataset
207
+ data_source.db[qualified_collection_name]
195
208
  end
196
209
 
197
210
  def qualified_collection_name
198
- schema.nil? ? Sequel[self.class.collection_name] : Sequel[schema][self.class.collection_name]
211
+ data_source.qualify self.class.collection_name
199
212
  end
200
213
 
214
+ def qualify(collection_name, data_source_name: nil)
215
+ data_source_name ||= self.class.data_source_name
216
+ environment.qualify data_source_name, collection_name
217
+ end
218
+
201
219
  def default_query
202
220
  Query.new collection_class: self.class,
203
221
  dataset: dataset.select_all(self.class.collection_name),
204
- schema: schema
222
+ environment: environment
205
223
  end
206
224
 
207
225
  def build_query(filter=nil, &block)
208
226
  raise ArgumentError, 'must specify filter hash or block' if filter.nil? && block.nil?
209
227
 
210
228
  if filter
211
- default_query.where filter
229
+ default_query.where(filter)
212
230
  else
213
231
  block.arity == 0 ? default_query.instance_eval(&block) : block.call(default_query)
214
232
  end
215
233
  end
216
234
 
235
+ def transform_attributes_to_db(attributes)
236
+ attributes.each_with_object({}) do |(attribute_name, value), result|
237
+ transformed_value = Rasti::DB.to_db data_source.db, qualified_collection_name, attribute_name, value
238
+ result[attribute_name] = transformed_value
239
+ end
240
+ end
241
+
217
242
  def split_related_attributes(attributes)
218
243
  relation_names = self.class.relations.values.select(&:many_to_many?).map(&:name)
219
244
 
@@ -239,18 +264,22 @@ module Rasti
239
264
  end
240
265
 
241
266
  relations.select { |r| r.one_to_many? || r.one_to_one? }.each do |relation|
242
- relation_collection_name = with_schema(relation.target_collection_class.collection_name)
243
- relations_ids = db[relation_collection_name].where(relation.foreign_key => primary_keys)
244
- .select(relation.target_collection_class.primary_key)
245
- .map(relation.target_collection_class.primary_key)
267
+ relation_data_source = environment.data_source_of relation.target_collection_class
268
+ relation_collection_name = relation_data_source.qualify relation.target_collection_class.collection_name
246
269
 
247
- target_collection = relation.target_collection_class.new db, schema
270
+ relations_ids = relation_data_source.db[relation_collection_name]
271
+ .where(relation.foreign_key => primary_keys)
272
+ .select(relation.target_collection_class.primary_key)
273
+ .map(relation.target_collection_class.primary_key)
274
+
275
+ target_collection = relation.target_collection_class.new environment
248
276
  target_collection.delete_cascade(*relations_ids) unless relations_ids.empty?
249
277
  end
250
278
  end
251
279
 
252
280
  def insert_relation_table(relation, primary_key, relation_primary_keys)
253
- relation_collection_name = relation.qualified_relation_collection_name(schema)
281
+ relation_data_source = environment.data_source relation.relation_data_source_name
282
+ relation_collection_name = relation_data_source.qualify relation.relation_collection_name
254
283
 
255
284
  values = relation_primary_keys.map do |relation_pk|
256
285
  {
@@ -259,12 +288,14 @@ module Rasti
259
288
  }
260
289
  end
261
290
 
262
- db[relation_collection_name].multi_insert values
291
+ relation_data_source.db[relation_collection_name].multi_insert values
263
292
  end
264
293
 
265
294
  def delete_relation_table(relation, primary_keys, relation_primary_keys=nil)
266
- relation_collection_name = relation.qualified_relation_collection_name(schema)
267
- ds = db[relation_collection_name].where(relation.source_foreign_key => primary_keys)
295
+ relation_data_source = environment.data_source relation.relation_data_source_name
296
+ relation_collection_name = relation_data_source.qualify relation.relation_collection_name
297
+
298
+ ds = relation_data_source.db[relation_collection_name].where(relation.source_foreign_key => primary_keys)
268
299
  ds = ds.where(relation.target_foreign_key => relation_primary_keys) if relation_primary_keys
269
300
  ds.delete
270
301
  end
@@ -0,0 +1,22 @@
1
+ module Rasti
2
+ module DB
3
+ class ComputedAttribute
4
+
5
+ attr_reader :identifier
6
+
7
+ def initialize(identifier, &join)
8
+ @identifier = identifier
9
+ @join = join
10
+ end
11
+
12
+ def apply_join(dataset)
13
+ join ? join.call(dataset) : dataset
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :join
19
+
20
+ end
21
+ end
22
+ end