rasti-db 1.5.0 → 2.3.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.
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