rasti-db 1.3.0 → 2.0.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.
- checksums.yaml +4 -4
- data/.travis.yml +7 -12
- data/README.md +77 -21
- data/lib/rasti/db.rb +2 -1
- data/lib/rasti/db/collection.rb +70 -46
- data/lib/rasti/db/data_source.rb +18 -0
- data/lib/rasti/db/environment.rb +32 -0
- data/lib/rasti/db/model.rb +4 -0
- data/lib/rasti/db/query.rb +71 -37
- data/lib/rasti/db/relations/base.rb +22 -8
- data/lib/rasti/db/relations/graph.rb +129 -0
- data/lib/rasti/db/relations/many_to_many.rb +59 -25
- data/lib/rasti/db/relations/many_to_one.rb +17 -12
- data/lib/rasti/db/relations/one_to_many.rb +27 -16
- data/lib/rasti/db/version.rb +1 -1
- data/rasti-db.gemspec +3 -8
- data/spec/collection_spec.rb +223 -52
- data/spec/minitest_helper.rb +50 -14
- data/spec/model_spec.rb +9 -1
- data/spec/query_spec.rb +199 -77
- data/spec/relations_spec.rb +27 -7
- metadata +25 -10
- data/lib/rasti/db/helpers.rb +0 -16
- data/lib/rasti/db/relations/graph_builder.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b41f6198798c9bee6da1f94742378639bb3b900f3bda6471f791b42a71a24c7
|
4
|
+
data.tar.gz: f470e1f1ef5673546e54e6c144e16ebba577b0f848b8fd59c8822489808a7cbf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5e836d580a42656efe1d78ee2e193449edd1adf45dbccd0a4b4c9c4d6f263a0d92e844a142abb908a683a8dcad4865d5926aec10b5a0260c0240425dcd063dd
|
7
|
+
data.tar.gz: 71148abafe56a6fe60613f2ebfa38138df2aaa656bdbe7116a834d41f3920fb49b97bfffac3235a520a6ffbb293184310d2d7fcefa552178d1cb767b94ce8e4d
|
data/.travis.yml
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
language: ruby
|
2
2
|
|
3
3
|
rvm:
|
4
|
-
- 2.0
|
5
4
|
- 2.1
|
6
5
|
- 2.2
|
7
|
-
- 2.3
|
8
|
-
- 2.4
|
9
|
-
- 2.5
|
10
|
-
- 2.6
|
11
|
-
-
|
12
|
-
- jruby-9.
|
6
|
+
- 2.3
|
7
|
+
- 2.4
|
8
|
+
- 2.5
|
9
|
+
- 2.6
|
10
|
+
- 2.7
|
11
|
+
- jruby-9.2.9.0
|
13
12
|
- ruby-head
|
14
13
|
- jruby-head
|
15
14
|
|
@@ -20,8 +19,4 @@ matrix:
|
|
20
19
|
- rvm: jruby-head
|
21
20
|
|
22
21
|
jdk:
|
23
|
-
- openjdk8
|
24
|
-
|
25
|
-
before_install:
|
26
|
-
- rvm all-gemsets do gem uninstall bundler -ax || true
|
27
|
-
- gem install bundler -v "< 2"
|
22
|
+
- openjdk8
|
data/README.md
CHANGED
@@ -4,7 +4,6 @@
|
|
4
4
|
[](https://travis-ci.org/gabynaiman/rasti-db)
|
5
5
|
[](https://coveralls.io/github/gabynaiman/rasti-db?branch=master)
|
6
6
|
[](https://codeclimate.com/github/gabynaiman/rasti-db)
|
7
|
-
[](https://gemnasium.com/gabynaiman/rasti-db)
|
8
7
|
|
9
8
|
Database collections and relations
|
10
9
|
|
@@ -29,49 +28,62 @@ Or install it yourself as:
|
|
29
28
|
### Database connection
|
30
29
|
|
31
30
|
```ruby
|
32
|
-
|
31
|
+
DB_1 = Sequel.connect ...
|
32
|
+
DB_2 = Sequel.connect ...
|
33
33
|
```
|
34
34
|
|
35
35
|
### Database schema
|
36
36
|
|
37
37
|
```ruby
|
38
|
-
|
38
|
+
DB_1.create_table :users do
|
39
39
|
primary_key :id
|
40
40
|
String :name, null: false, unique: true
|
41
41
|
end
|
42
42
|
|
43
|
-
|
43
|
+
DB_1.create_table :posts do
|
44
44
|
primary_key :id
|
45
45
|
String :title, null: false, unique: true
|
46
46
|
String :body, null: false
|
47
|
+
Integer :language_id, null: false, index: true
|
47
48
|
foreign_key :user_id, :users, null: false, index: true
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
+
DB_1.create_table :comments do
|
51
52
|
primary_key :id
|
52
53
|
String :text, null: false
|
53
54
|
foreign_key :user_id, :users, null: false, index: true
|
54
55
|
foreign_key :post_id, :posts, null: false, index: true
|
55
56
|
end
|
56
57
|
|
57
|
-
|
58
|
+
DB_1.create_table :categories do
|
58
59
|
primary_key :id
|
59
60
|
String :name, null: false, unique: true
|
60
61
|
end
|
61
62
|
|
62
|
-
|
63
|
+
DB_1.create_table :categories_posts do
|
63
64
|
foreign_key :category_id, :categories, null: false, index: true
|
64
65
|
foreign_key :post_id, :posts, null: false, index: true
|
65
66
|
primary_key [:category_id, :post_id]
|
66
67
|
end
|
67
68
|
|
68
|
-
|
69
|
+
DB_1.create_table :people do
|
69
70
|
String :document_number, null: false, primary_key: true
|
70
71
|
String :first_name, null: false
|
71
72
|
String :last_name, null: false
|
72
73
|
Date :birth_date, null: false
|
73
74
|
foreign_key :user_id, :users, null: false, unique: true
|
74
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
|
75
87
|
```
|
76
88
|
|
77
89
|
### Models
|
@@ -82,6 +94,7 @@ Post = Rasti::DB::Model[:id, :title, :body, :user_id, :user, :comments, :cat
|
|
82
94
|
Comment = Rasti::DB::Model[:id, :text, :user_id, :user, :post_id, :post]
|
83
95
|
Category = Rasti::DB::Model[:id, :name, :posts]
|
84
96
|
Person = Rasti::DB::Model[:document_number, :first_name, :last_name, :birth_date, :user_id, :user]
|
97
|
+
Language = Rasti::DB::Model[:id, :name, :people]
|
85
98
|
```
|
86
99
|
|
87
100
|
### Collections
|
@@ -106,9 +119,9 @@ class Posts < Rasti::DB::Collection
|
|
106
119
|
|
107
120
|
query :commented_by do |user_id|
|
108
121
|
chainable do
|
109
|
-
dataset.join(
|
110
|
-
.where(
|
111
|
-
.select_all(
|
122
|
+
dataset.join(qualify(:comments), post_id: :id)
|
123
|
+
.where(Sequel[:comments][:user_id] => user_id)
|
124
|
+
.select_all(:posts)
|
112
125
|
.distinct
|
113
126
|
end
|
114
127
|
end
|
@@ -130,19 +143,30 @@ class People < Rasti::DB::Collection
|
|
130
143
|
set_model Person
|
131
144
|
|
132
145
|
many_to_one :user
|
146
|
+
many_to_many :languages
|
147
|
+
end
|
148
|
+
|
149
|
+
class Languages < Rasti::DB::Collection
|
150
|
+
set_data_source_name :other
|
151
|
+
|
152
|
+
one_to_many :posts
|
153
|
+
many_to_many :people, collection: People, relation_data_source_name: :default
|
133
154
|
end
|
134
155
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
156
|
+
environment = Rasti::DB::Environment.new default: Rasti::DB::DataSource.new(DB_1),
|
157
|
+
other: Rasti::DB::DataSource.new(DB_2, 'custom_schema')
|
158
|
+
|
159
|
+
users = Users.new environment
|
160
|
+
posts = Posts.new environment
|
161
|
+
comments = Comments.new environment
|
162
|
+
categories = Categories.new environment
|
163
|
+
people = People.new environment
|
140
164
|
```
|
141
165
|
|
142
166
|
### Persistence
|
143
167
|
|
144
168
|
```ruby
|
145
|
-
|
169
|
+
DB_1.transaction do
|
146
170
|
id = users.insert name: 'User 1'
|
147
171
|
users.update id, name: 'User updated'
|
148
172
|
users.delete id
|
@@ -162,17 +186,49 @@ end
|
|
162
186
|
posts.all # => [Post, ...]
|
163
187
|
posts.first # => Post
|
164
188
|
posts.count # => 1
|
189
|
+
|
165
190
|
posts.where(id: [1,2]) # => [Post, ...]
|
166
191
|
posts.where{id > 1}.limit(10).offset(20) } # => [Post, ...]
|
167
|
-
|
168
|
-
posts.created_by(1) # => [Post, ...]
|
169
|
-
posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
|
170
|
-
posts.with_categories([1,2]) # => [Post, ...]
|
192
|
+
|
171
193
|
posts.where(id: [1,2]).raw # => [{id:1, ...}, {id:2, ...}]
|
172
194
|
posts.where(id: [1,2]).primary_keys # => [1,2]
|
173
195
|
posts.where(id: [1,2]).pluck(:id) # => [1,2]
|
174
196
|
posts.where(id: [1,2]).pluck(:id, :title) # => [[1, ...], [2, ...]]
|
197
|
+
|
198
|
+
posts.created_by(1) # => [Post, ...]
|
199
|
+
posts.created_by(1).entitled('...').commented_by(2) # => [Post, ...]
|
200
|
+
posts.with_categories([1,2]) # => [Post, ...]
|
201
|
+
|
202
|
+
posts.graph(:user, :language, :categories, 'comments.user') # => [Post(User, Language, [Categories, ...], [Comments(User)]), ...]
|
203
|
+
|
175
204
|
posts.join(:user).where(name: 'User 4') # => [Post, ...]
|
205
|
+
|
206
|
+
posts.select_attributes(:id, :title) # => [Post, ...]
|
207
|
+
posts.exclude_attributes(:id, :title) # => [Post, ...]
|
208
|
+
posts.all_attributes # => [Post, ...]
|
209
|
+
|
210
|
+
posts.graph('user.person').select_graph_attributes(user: [:id], 'user.person': [:last_name, :user_id]) # => [Post, ...]
|
211
|
+
posts.graph('user.person').exclude_graph_attributes(user: [:name], 'user.person': [:first_name, :last_name]) # => [Post, ...]
|
212
|
+
posts.graph('user.person').all_graph_attributes('user.person') # => [Post, ...]
|
213
|
+
```
|
214
|
+
### Natural Query Language
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
posts.nql('id = 1') # => Equal
|
218
|
+
posts.nql('id != 1') # => Not equal
|
219
|
+
posts.nql('title: My post') # => Include
|
220
|
+
posts.nql('title !: My post') # => Not include
|
221
|
+
posts.nql('title ~ My post') # => Insensitive like
|
222
|
+
posts.nql('id > 1') # => Greater
|
223
|
+
posts.nql('id >= 1') # => Greater or equal
|
224
|
+
posts.nql('id < 10') # => Less
|
225
|
+
posts.nql('id <= 10') # => Less or equal
|
226
|
+
|
227
|
+
posts.nql('id = 1 | id = 2') # => Or
|
228
|
+
posts.nql('id > 1 & title: "My post"') # => And
|
229
|
+
posts.nql('(id > 3 & id < 10) | title: "My post"') # => Precedence
|
230
|
+
|
231
|
+
posts.nql('comments.user.person.document_number = 7') # => Nested
|
176
232
|
```
|
177
233
|
|
178
234
|
## Development
|
data/lib/rasti/db.rb
CHANGED
@@ -3,7 +3,9 @@ require 'consty'
|
|
3
3
|
require 'time'
|
4
4
|
require 'timing'
|
5
5
|
require 'treetop'
|
6
|
+
require 'hierarchical_graph'
|
6
7
|
require 'class_config'
|
8
|
+
require 'hash_ext'
|
7
9
|
require 'multi_require'
|
8
10
|
|
9
11
|
module Rasti
|
@@ -12,7 +14,6 @@ module Rasti
|
|
12
14
|
extend MultiRequire
|
13
15
|
extend ClassConfig
|
14
16
|
|
15
|
-
require_relative 'db/helpers'
|
16
17
|
require_relative 'db/query'
|
17
18
|
require_relative_pattern 'db/relations/*'
|
18
19
|
require_relative_pattern 'db/type_converters/postgres_types/*'
|
data/lib/rasti/db/collection.rb
CHANGED
@@ -2,10 +2,7 @@ module Rasti
|
|
2
2
|
module DB
|
3
3
|
class Collection
|
4
4
|
|
5
|
-
QUERY_METHODS =
|
6
|
-
|
7
|
-
include Enumerable
|
8
|
-
include Helpers::WithSchema
|
5
|
+
QUERY_METHODS = Query.public_instance_methods - Object.public_instance_methods
|
9
6
|
|
10
7
|
class << self
|
11
8
|
|
@@ -16,8 +13,8 @@ module Rasti
|
|
16
13
|
@collection_name ||= underscore(demodulize(name)).to_sym
|
17
14
|
end
|
18
15
|
|
19
|
-
def
|
20
|
-
@
|
16
|
+
def collection_attributes
|
17
|
+
@collection_attributes ||= model.attributes - relations.keys
|
21
18
|
end
|
22
19
|
|
23
20
|
def primary_key
|
@@ -38,12 +35,16 @@ module Rasti
|
|
38
35
|
end
|
39
36
|
end
|
40
37
|
|
38
|
+
def data_source_name
|
39
|
+
@data_source_name ||= superclass.respond_to?(:data_source_name) ? superclass.data_source_name : :default
|
40
|
+
end
|
41
|
+
|
41
42
|
def relations
|
42
|
-
@relations ||=
|
43
|
+
@relations ||= Hash::Indifferent.new
|
43
44
|
end
|
44
45
|
|
45
46
|
def queries
|
46
|
-
@queries ||=
|
47
|
+
@queries ||= Hash::Indifferent.new
|
47
48
|
end
|
48
49
|
|
49
50
|
private
|
@@ -64,11 +65,17 @@ module Rasti
|
|
64
65
|
@model = model
|
65
66
|
end
|
66
67
|
|
68
|
+
def set_data_source_name(name)
|
69
|
+
@data_source_name = name.to_sym
|
70
|
+
end
|
71
|
+
|
67
72
|
[Relations::OneToMany, Relations::ManyToOne, Relations::ManyToMany, Relations::OneToOne].each do |relation_class|
|
68
73
|
define_method underscore(demodulize(relation_class.name)) do |name, options={}|
|
69
74
|
relations[name] = relation_class.new name, self, options
|
70
75
|
|
71
|
-
|
76
|
+
query_name = relations[name].to_many? ? name : pluralize(name)
|
77
|
+
|
78
|
+
query "with_#{query_name}" do |primary_keys|
|
72
79
|
with_related name, primary_keys
|
73
80
|
end
|
74
81
|
end
|
@@ -80,25 +87,24 @@ module Rasti
|
|
80
87
|
queries[name] = lambda || block
|
81
88
|
|
82
89
|
define_method name do |*args|
|
83
|
-
|
90
|
+
default_query.instance_exec(*args, &self.class.queries.fetch(name))
|
84
91
|
end
|
85
92
|
end
|
86
93
|
|
87
94
|
end
|
88
95
|
|
89
|
-
|
90
|
-
|
91
|
-
def initialize(db, schema=nil)
|
92
|
-
@db = db
|
93
|
-
@schema = schema ? schema.to_sym : nil
|
96
|
+
def initialize(environment)
|
97
|
+
@environment = environment
|
94
98
|
end
|
95
99
|
|
96
|
-
|
97
|
-
|
100
|
+
QUERY_METHODS.each do |method|
|
101
|
+
define_method method do |*args, &block|
|
102
|
+
default_query.public_send method, *args, &block
|
103
|
+
end
|
98
104
|
end
|
99
105
|
|
100
106
|
def insert(attributes)
|
101
|
-
db.transaction do
|
107
|
+
data_source.db.transaction do
|
102
108
|
db_attributes = transform_attributes_to_db attributes
|
103
109
|
collection_attributes, relations_primary_keys = split_related_attributes db_attributes
|
104
110
|
primary_key = dataset.insert collection_attributes
|
@@ -121,7 +127,7 @@ module Rasti
|
|
121
127
|
end
|
122
128
|
|
123
129
|
def update(primary_key, attributes)
|
124
|
-
db.transaction do
|
130
|
+
data_source.db.transaction do
|
125
131
|
db_attributes = transform_attributes_to_db attributes
|
126
132
|
collection_attributes, relations_primary_keys = split_related_attributes db_attributes
|
127
133
|
dataset.where(self.class.primary_key => primary_key).update(collection_attributes) unless collection_attributes.empty?
|
@@ -147,7 +153,7 @@ module Rasti
|
|
147
153
|
end
|
148
154
|
|
149
155
|
def delete_relations(primary_key, relations)
|
150
|
-
db.transaction do
|
156
|
+
data_source.db.transaction do
|
151
157
|
relations.each do |relation_name, relation_primary_keys|
|
152
158
|
relation = self.class.relations[relation_name]
|
153
159
|
delete_relation_table relation, primary_key, relation_primary_keys
|
@@ -157,7 +163,7 @@ module Rasti
|
|
157
163
|
end
|
158
164
|
|
159
165
|
def delete_cascade(*primary_keys)
|
160
|
-
db.transaction do
|
166
|
+
data_source.db.transaction do
|
161
167
|
delete_cascade_relations primary_keys
|
162
168
|
bulk_delete { |q| q.where self.class.primary_key => primary_keys }
|
163
169
|
end
|
@@ -172,12 +178,6 @@ module Rasti
|
|
172
178
|
where(self.class.primary_key => primary_key).graph(*relations).first
|
173
179
|
end
|
174
180
|
|
175
|
-
QUERY_METHODS.each do |method|
|
176
|
-
define_method method do |*args, &block|
|
177
|
-
query.public_send method, *args, &block
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
181
|
def exists?(filter=nil, &block)
|
182
182
|
build_query(filter, &block).any?
|
183
183
|
end
|
@@ -188,27 +188,45 @@ module Rasti
|
|
188
188
|
|
189
189
|
private
|
190
190
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
191
|
+
attr_reader :environment
|
192
|
+
|
193
|
+
def data_source
|
194
|
+
@data_source ||= environment.data_source_of self.class
|
195
|
+
end
|
196
|
+
|
197
|
+
def dataset
|
198
|
+
data_source.db[qualified_collection_name]
|
196
199
|
end
|
197
200
|
|
198
201
|
def qualified_collection_name
|
199
|
-
|
202
|
+
data_source.qualify self.class.collection_name
|
200
203
|
end
|
201
204
|
|
202
|
-
def
|
203
|
-
|
205
|
+
def qualify(collection_name, data_source_name: nil)
|
206
|
+
data_source_name ||= self.class.data_source_name
|
207
|
+
environment.qualify data_source_name, collection_name
|
208
|
+
end
|
209
|
+
|
210
|
+
def default_query
|
211
|
+
Query.new collection_class: self.class,
|
212
|
+
dataset: dataset.select_all(self.class.collection_name),
|
213
|
+
environment: environment
|
204
214
|
end
|
205
215
|
|
206
216
|
def build_query(filter=nil, &block)
|
207
217
|
raise ArgumentError, 'must specify filter hash or block' if filter.nil? && block.nil?
|
218
|
+
|
208
219
|
if filter
|
209
|
-
|
220
|
+
default_query.where(filter)
|
210
221
|
else
|
211
|
-
block.arity == 0 ?
|
222
|
+
block.arity == 0 ? default_query.instance_eval(&block) : block.call(default_query)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def transform_attributes_to_db(attributes)
|
227
|
+
attributes.each_with_object({}) do |(attribute_name, value), result|
|
228
|
+
transformed_value = Rasti::DB.to_db data_source.db, qualified_collection_name, attribute_name, value
|
229
|
+
result[attribute_name] = transformed_value
|
212
230
|
end
|
213
231
|
end
|
214
232
|
|
@@ -237,18 +255,22 @@ module Rasti
|
|
237
255
|
end
|
238
256
|
|
239
257
|
relations.select { |r| r.one_to_many? || r.one_to_one? }.each do |relation|
|
240
|
-
|
241
|
-
|
242
|
-
.select(relation.target_collection_class.primary_key)
|
243
|
-
.map(relation.target_collection_class.primary_key)
|
258
|
+
relation_data_source = environment.data_source_of relation.target_collection_class
|
259
|
+
relation_collection_name = relation_data_source.qualify relation.target_collection_class.collection_name
|
244
260
|
|
245
|
-
|
261
|
+
relations_ids = relation_data_source.db[relation_collection_name]
|
262
|
+
.where(relation.foreign_key => primary_keys)
|
263
|
+
.select(relation.target_collection_class.primary_key)
|
264
|
+
.map(relation.target_collection_class.primary_key)
|
265
|
+
|
266
|
+
target_collection = relation.target_collection_class.new environment
|
246
267
|
target_collection.delete_cascade(*relations_ids) unless relations_ids.empty?
|
247
268
|
end
|
248
269
|
end
|
249
270
|
|
250
271
|
def insert_relation_table(relation, primary_key, relation_primary_keys)
|
251
|
-
|
272
|
+
relation_data_source = environment.data_source relation.relation_data_source_name
|
273
|
+
relation_collection_name = relation_data_source.qualify relation.relation_collection_name
|
252
274
|
|
253
275
|
values = relation_primary_keys.map do |relation_pk|
|
254
276
|
{
|
@@ -257,12 +279,14 @@ module Rasti
|
|
257
279
|
}
|
258
280
|
end
|
259
281
|
|
260
|
-
db[relation_collection_name].multi_insert values
|
282
|
+
relation_data_source.db[relation_collection_name].multi_insert values
|
261
283
|
end
|
262
284
|
|
263
285
|
def delete_relation_table(relation, primary_keys, relation_primary_keys=nil)
|
264
|
-
|
265
|
-
|
286
|
+
relation_data_source = environment.data_source relation.relation_data_source_name
|
287
|
+
relation_collection_name = relation_data_source.qualify relation.relation_collection_name
|
288
|
+
|
289
|
+
ds = relation_data_source.db[relation_collection_name].where(relation.source_foreign_key => primary_keys)
|
266
290
|
ds = ds.where(relation.target_foreign_key => relation_primary_keys) if relation_primary_keys
|
267
291
|
ds.delete
|
268
292
|
end
|