actn-db 0.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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +12 -0
- data/actn-db.gemspec +30 -0
- data/db/1_db.sql +54 -0
- data/db/__functions.sql +472 -0
- data/db/__setup.sql +37 -0
- data/db/lib/_0_actn.js +23 -0
- data/db/lib/_1_underscore.js +1 -0
- data/db/lib/_2_jjv.js +739 -0
- data/db/lib/_3_inflections.js +634 -0
- data/db/lib/_4_builder.coffee +136 -0
- data/db/lib/_4_builder.js +218 -0
- data/db/schemas/model.json +76 -0
- data/lib/actn/core_ext/hash.rb +10 -0
- data/lib/actn/core_ext/kernel.rb +8 -0
- data/lib/actn/core_ext/string.rb +14 -0
- data/lib/actn/db.rb +24 -0
- data/lib/actn/db/mod.rb +175 -0
- data/lib/actn/db/model.rb +25 -0
- data/lib/actn/db/pg.rb +88 -0
- data/lib/actn/db/set.rb +67 -0
- data/lib/actn/db/tasks/db.rake +96 -0
- data/lib/actn/db/version.rb +5 -0
- data/lib/actn/paths.rb +38 -0
- data/test/actn/test_mod.rb +54 -0
- data/test/actn/test_model.rb +49 -0
- data/test/actn/test_pg_funcs.rb +71 -0
- data/test/actn/test_set.rb +57 -0
- data/test/minitest_helper.rb +18 -0
- metadata +208 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b41ff0c4461fa9cde659fe55e7ac9b2cd974350d
|
4
|
+
data.tar.gz: 94e83df6646636f8ec9eb822ccbe3675189a6538
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e1972c75f05e55e51c5ccabb2ab5a29bf4e748f206c675cae168b798d75a7ca41ea14d06ec906ad0496249c5762c3fc1494aef2af1793f21129333caced63bdc
|
7
|
+
data.tar.gz: 1d6c1ceaf715844dd3dbd893df4bb273362307dbe0432c50173820ac1a1d6711c28c6e256b558a0af92bb23a424818ed8347c1cfdf938466f3ee3b3b87ee5dbd
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Onur Uyar
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Actn::DB
|
2
|
+
|
3
|
+
Handy PLV8 Tools
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'actn-db'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install actn-db
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( http://github.com/<my-github-username>/actn-db/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.test_files = FileList['test/**/test*.rb']
|
7
|
+
end
|
8
|
+
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
ENV['DATABASE_URL'] ||= "postgres://localhost:5432/actn_#{ENV['RACK_ENV'] ||= "development"}"
|
12
|
+
load "actn/db/tasks/db.rake"
|
data/actn-db.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'actn/db/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "actn-db"
|
8
|
+
spec.version = Actn::DB::VERSION
|
9
|
+
spec.authors = ["Onur Uyar"]
|
10
|
+
spec.email = ["me@onuruyar.com"]
|
11
|
+
spec.summary = %q{Actn.io DB}
|
12
|
+
spec.homepage = "https://github.com/hackberry-gh/actn-db"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
21
|
+
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "minitest"
|
23
|
+
spec.add_development_dependency "minitest-reporters"
|
24
|
+
|
25
|
+
spec.add_dependency "coffee-script"
|
26
|
+
spec.add_dependency "em-pg-client"
|
27
|
+
spec.add_dependency "activemodel"
|
28
|
+
spec.add_dependency "oj"
|
29
|
+
spec.add_dependency "bcrypt"
|
30
|
+
end
|
data/db/1_db.sql
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
CREATE SCHEMA core;
|
2
|
+
SET search_path TO core,public;
|
3
|
+
|
4
|
+
SELECT plv8_startup();
|
5
|
+
|
6
|
+
SELECT __create_table('core','models');
|
7
|
+
SELECT __create_index('core','models', '{"cols": {"name": "text"},"unique": true}');
|
8
|
+
|
9
|
+
|
10
|
+
CREATE or REPLACE FUNCTION model_callbacks() RETURNS trigger AS
|
11
|
+
$$
|
12
|
+
table_name = (NEW?.data?.name or OLD?.data?.name)?.tableize()
|
13
|
+
table_schema = (NEW?.data?.table_schema or OLD?.data?.table_schema) or "public"
|
14
|
+
|
15
|
+
return if table_schema is "core"
|
16
|
+
|
17
|
+
# plv8.elog(NOTICE,"MODEL CALLBACKS",table_schema,JSON.stringify(NEW?.data or OLD?.data))
|
18
|
+
|
19
|
+
mapper = (ind) -> _.keys(ind.cols)
|
20
|
+
differ = (_ind) ->
|
21
|
+
(ind) ->
|
22
|
+
_.isEmpty( _.difference( _.keys(ind.cols), _.flatten( _.map( _ind.data?.indexes, mapper ) ) ) )
|
23
|
+
|
24
|
+
switch TG_OP
|
25
|
+
when "INSERT"
|
26
|
+
plv8.execute "SELECT __create_table($1,$2)",[table_schema , table_name]
|
27
|
+
|
28
|
+
plv8.execute "SELECT __create_index($1,$2,$3)", [table_schema, table_name, {cols: {path: "text" }}]
|
29
|
+
|
30
|
+
for indopts in NEW?.data?.indexes or []
|
31
|
+
plv8.execute "SELECT __create_index($1,$2,$3)", [table_schema, table_name, indopts]
|
32
|
+
|
33
|
+
when "UPDATE"
|
34
|
+
|
35
|
+
diff = _.reject( OLD?.data?.indexes, differ(NEW) )
|
36
|
+
|
37
|
+
for indopts in diff
|
38
|
+
plv8.execute "SELECT __drop_index($1,$2,$3)", [table_schema, table_name, indopts]
|
39
|
+
|
40
|
+
diff = _.reject( NEW?.data?.indexes, differ(OLD) )
|
41
|
+
|
42
|
+
for indopts in diff
|
43
|
+
plv8.execute "SELECT __create_index($1,$2,$3)", [table_schema, table_name, indopts]
|
44
|
+
|
45
|
+
when "DELETE"
|
46
|
+
for indopts in Old?.data?.indexes or []
|
47
|
+
plv8.execute "SELECT __drop_index($1,$2,$3)", [table_schema, table_name, indopts]
|
48
|
+
plv8.execute "SELECT __drop_table($1,$2)",[table_schema , table_name]
|
49
|
+
|
50
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
51
|
+
|
52
|
+
CREATE TRIGGER core_models_callback_trigger
|
53
|
+
AFTER INSERT OR UPDATE OR DELETE ON core.models
|
54
|
+
FOR EACH ROW EXECUTE PROCEDURE model_callbacks();
|
data/db/__functions.sql
ADDED
@@ -0,0 +1,472 @@
|
|
1
|
+
-- PLV8 Functions
|
2
|
+
|
3
|
+
CREATE or REPLACE FUNCTION __json(_data json, _key text) RETURNS JSON AS $$
|
4
|
+
ret = actn.valueAt(_data,_key)
|
5
|
+
return null unless ret?
|
6
|
+
return JSON.stringify(ret)
|
7
|
+
|
8
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
CREATE or REPLACE FUNCTION __string(_data json, _key text) RETURNS TEXT AS $$
|
15
|
+
ret = actn.valueAt(_data,_key)
|
16
|
+
return null unless ret?
|
17
|
+
return ret.toString()
|
18
|
+
|
19
|
+
$$ LANGUAGE plcoffee IMMUTABLE STRICT;
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
CREATE or REPLACE FUNCTION __integer(_data json, _key text) RETURNS INT AS $$
|
26
|
+
ret = actn.valueAt(_data,_key)
|
27
|
+
return null unless ret?
|
28
|
+
return parseInt(ret)
|
29
|
+
|
30
|
+
$$ LANGUAGE plcoffee IMMUTABLE STRICT;
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
CREATE or REPLACE FUNCTION __integer_array(_data json, _key text) RETURNS INT[] AS $$
|
37
|
+
ret = actn.valueAt(_data,_key)
|
38
|
+
return null unless ret?
|
39
|
+
return (if ret instanceof Array then ret else [ret])
|
40
|
+
|
41
|
+
$$ LANGUAGE plcoffee IMMUTABLE STRICT;
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
CREATE or REPLACE FUNCTION __float(_data json, _key text) RETURNS DOUBLE PRECISION AS $$
|
48
|
+
ret = actn.valueAt(_data,_key)
|
49
|
+
return null unless ret?
|
50
|
+
return parseFloat(ret)
|
51
|
+
|
52
|
+
$$ LANGUAGE plcoffee IMMUTABLE STRICT;
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
CREATE or REPLACE FUNCTION __bool(_data json, _key text) RETURNS BOOLEAN AS $$
|
59
|
+
ret = actn.valueAt(_data,_key)
|
60
|
+
return null unless ret?
|
61
|
+
return !!ret
|
62
|
+
|
63
|
+
$$ LANGUAGE plcoffee IMMUTABLE STRICT;
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
CREATE or REPLACE FUNCTION __timestamp(_data json, _key text) RETURNS TIMESTAMP AS $$
|
70
|
+
ret = actn.valueAt(_data,_key)
|
71
|
+
return null unless ret?
|
72
|
+
return new Date(ret)
|
73
|
+
|
74
|
+
$$ LANGUAGE plcoffee IMMUTABLE STRICT;
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
CREATE or REPLACE FUNCTION __patch(_data json, _value json, _sync boolean) RETURNS JSON AS $$
|
81
|
+
|
82
|
+
data = _data
|
83
|
+
changes = _value
|
84
|
+
isObject = false
|
85
|
+
|
86
|
+
sync = if _sync? then _sync else true
|
87
|
+
|
88
|
+
defaults = _.pick( data, _.keys( JSON.parse( plv8.find_function('__defaults')() ) ) )
|
89
|
+
|
90
|
+
for k of changes
|
91
|
+
if data.hasOwnProperty(k)
|
92
|
+
isObject = typeof (data[k]) is "object" and typeof (changes[k]) is "object"
|
93
|
+
data[k] = if isObject and sync then _.extend(data[k], changes[k]) else changes[k]
|
94
|
+
else
|
95
|
+
data[k] = changes[k]
|
96
|
+
|
97
|
+
unless sync
|
98
|
+
for k of data
|
99
|
+
delete data[k] unless changes[k]?
|
100
|
+
|
101
|
+
_.extend(data, defaults)
|
102
|
+
|
103
|
+
return JSON.stringify(data)
|
104
|
+
|
105
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
CREATE or REPLACE FUNCTION __select(_data json, _fields text) RETURNS JSON AS $$
|
112
|
+
|
113
|
+
data = _data
|
114
|
+
fields = _fields
|
115
|
+
ret = _.pick(data,fields.split(","))
|
116
|
+
|
117
|
+
return JSON.stringify(ret)
|
118
|
+
|
119
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
CREATE or REPLACE FUNCTION __push(_data json, _key text, _value json) RETURNS JSON AS $$
|
126
|
+
|
127
|
+
data = _data
|
128
|
+
value = _value
|
129
|
+
keys = _key.split(".")
|
130
|
+
len = keys.length
|
131
|
+
last_field = data
|
132
|
+
field = data
|
133
|
+
i = 0
|
134
|
+
|
135
|
+
while i < len
|
136
|
+
last_field = field
|
137
|
+
field = field[keys[i]] if field
|
138
|
+
++i
|
139
|
+
if field
|
140
|
+
field.push value
|
141
|
+
else
|
142
|
+
value = [value] unless value instanceof Array
|
143
|
+
last_field[keys.pop()] = value
|
144
|
+
|
145
|
+
return JSON.stringify(data)
|
146
|
+
|
147
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
CREATE or REPLACE FUNCTION __uuid() RETURNS JSON AS $$
|
154
|
+
|
155
|
+
ary = plv8.execute 'SELECT uuid_generate_v4() as uuid;'
|
156
|
+
return JSON.stringify(ary[0])
|
157
|
+
|
158
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
|
164
|
+
|
165
|
+
CREATE or REPLACE FUNCTION __defaults() RETURNS JSON AS $$
|
166
|
+
|
167
|
+
uuid = JSON.parse(plv8.find_function('__uuid')())
|
168
|
+
timestamp = new Date()
|
169
|
+
return JSON.stringify({uuid: uuid.uuid, created_at: timestamp, updated_at: timestamp})
|
170
|
+
|
171
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
CREATE or REPLACE FUNCTION __create_table(schema_name text, table_name text) RETURNS JSON AS $$
|
178
|
+
|
179
|
+
plv8.execute """
|
180
|
+
CREATE TABLE #{schema_name}.#{table_name} (
|
181
|
+
id serial NOT NULL,
|
182
|
+
data json DEFAULT __uuid() NOT NULL,
|
183
|
+
CONSTRAINT #{schema_name}_#{table_name}_pkey PRIMARY KEY (id));
|
184
|
+
|
185
|
+
CREATE UNIQUE INDEX indx_#{schema_name}_#{table_name}_unique_uuid ON #{schema_name}.#{table_name} (__string(data,'uuid'));
|
186
|
+
"""
|
187
|
+
return JSON.stringify(table_name)
|
188
|
+
|
189
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
190
|
+
|
191
|
+
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
CREATE or REPLACE FUNCTION __drop_table(schema_name text, table_name text) RETURNS JSON AS $$
|
196
|
+
|
197
|
+
plv8.execute "DROP TABLE IF EXISTS #{schema_name}.#{table_name} CASCADE;"
|
198
|
+
return JSON.stringify(table_name)
|
199
|
+
|
200
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
201
|
+
|
202
|
+
|
203
|
+
|
204
|
+
|
205
|
+
|
206
|
+
CREATE or REPLACE FUNCTION __create_index(schema_name text, table_name text, optns json) RETURNS JSON AS $$
|
207
|
+
|
208
|
+
index_name = "indx_#{schema_name}_#{table_name}"
|
209
|
+
for name, type of optns.cols
|
210
|
+
index_name += "_#{name}"
|
211
|
+
|
212
|
+
sql = ["CREATE"]
|
213
|
+
sql.push "UNIQUE" if optns.unique
|
214
|
+
sql.push "INDEX"
|
215
|
+
sql.push "CONCURRENTLY" if optns.concurrently
|
216
|
+
sql.push "#{index_name} on #{schema_name}.#{table_name}"
|
217
|
+
sql.push "("
|
218
|
+
cols = []
|
219
|
+
for name, type of optns.cols
|
220
|
+
meth = "__#{if type is 'text' then 'string' else type}"
|
221
|
+
cols.push "#{meth}(data,'#{name}'::#{type})"
|
222
|
+
sql.push cols.join(",")
|
223
|
+
sql.push ")"
|
224
|
+
|
225
|
+
sql = sql.join(" ")
|
226
|
+
|
227
|
+
plv8.execute(sql)
|
228
|
+
|
229
|
+
return JSON.stringify(index_name)
|
230
|
+
|
231
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
232
|
+
|
233
|
+
|
234
|
+
|
235
|
+
|
236
|
+
|
237
|
+
CREATE or REPLACE FUNCTION __drop_index(schema_name text, table_name text, optns json) RETURNS JSON AS $$
|
238
|
+
|
239
|
+
index_name = "indx_#{schema_name}_#{table_name}"
|
240
|
+
for name, type of optns.cols
|
241
|
+
index_name += "_#{name}"
|
242
|
+
|
243
|
+
plv8.execute("DROP INDEX IF EXISTS #{index_name}")
|
244
|
+
|
245
|
+
return JSON.stringify(index_name)
|
246
|
+
|
247
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
248
|
+
|
249
|
+
|
250
|
+
|
251
|
+
|
252
|
+
|
253
|
+
|
254
|
+
-- ##
|
255
|
+
-- # Select data
|
256
|
+
-- # SELECT query(_schema_name, _table_name, {where: {uuid: "12345"}});
|
257
|
+
|
258
|
+
CREATE or REPLACE FUNCTION __query(_schema_name text, _table_name text, _query json) RETURNS json AS $$
|
259
|
+
|
260
|
+
search_path = if _schema_name is "public" then _schema_name else "#{_schema_name}, public"
|
261
|
+
|
262
|
+
builder = new actn.Builder(_schema_name, _table_name, search_path, _query)
|
263
|
+
|
264
|
+
[sql,params] = builder.build_select()
|
265
|
+
|
266
|
+
rows = plv8.execute(sql,params)
|
267
|
+
|
268
|
+
builder = null
|
269
|
+
|
270
|
+
if _query?.select?.indexOf('COUNT') > -1
|
271
|
+
result = rows
|
272
|
+
else
|
273
|
+
result = _.pluck(rows,'data')
|
274
|
+
|
275
|
+
|
276
|
+
return JSON.stringify(result)
|
277
|
+
|
278
|
+
$$ LANGUAGE plcoffee STABLE;
|
279
|
+
|
280
|
+
|
281
|
+
|
282
|
+
|
283
|
+
|
284
|
+
-- ##
|
285
|
+
-- # Insert ot update row through validation!
|
286
|
+
-- # SELECT upsert(validate('User', '{"name":"foo"}'));
|
287
|
+
|
288
|
+
CREATE or REPLACE FUNCTION __upsert(_schema_name text, _table_name text, _data json) RETURNS json AS $$
|
289
|
+
|
290
|
+
# plv8.elog(NOTICE,"UPSERT",JSON.stringify(_data))
|
291
|
+
|
292
|
+
return JSON.stringify(_data) if _data.errors?
|
293
|
+
|
294
|
+
data = _data
|
295
|
+
|
296
|
+
search_path = if _schema_name is "public" then _schema_name else "#{_schema_name},public"
|
297
|
+
|
298
|
+
if data.uuid?
|
299
|
+
|
300
|
+
query = { where: { uuid: data.uuid } }
|
301
|
+
|
302
|
+
builder = new actn.Builder(_schema_name, _table_name, search_path, query )
|
303
|
+
|
304
|
+
[sql,params] = builder.build_update(data)
|
305
|
+
|
306
|
+
else
|
307
|
+
|
308
|
+
builder = new actn.Builder(_schema_name, _table_name, search_path, {})
|
309
|
+
|
310
|
+
[sql,params] = builder.build_insert(data)
|
311
|
+
|
312
|
+
|
313
|
+
# plan = plv8.prepare(sql, ['json','bool','text'])
|
314
|
+
|
315
|
+
# plv8.elog(NOTICE,sql,JSON.stringify(params))
|
316
|
+
|
317
|
+
rows = plv8.execute(sql, params)
|
318
|
+
|
319
|
+
result = _.pluck(rows,'data')
|
320
|
+
|
321
|
+
result = result[0] if result.length is 1
|
322
|
+
|
323
|
+
builder = null
|
324
|
+
|
325
|
+
return JSON.stringify(result)
|
326
|
+
|
327
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
328
|
+
|
329
|
+
|
330
|
+
|
331
|
+
|
332
|
+
|
333
|
+
-- ##
|
334
|
+
-- # Delete single row by uuid
|
335
|
+
-- # SELECT remove('users',uuid-1234567);
|
336
|
+
|
337
|
+
CREATE or REPLACE FUNCTION __update(_schema_name text, _table_name text, _data json, _cond json) RETURNS json AS $$
|
338
|
+
|
339
|
+
return JSON.stringify(_data) if _data.errors?
|
340
|
+
|
341
|
+
search_path = if _schema_name is "public" then _schema_name else "#{_schema_name},public"
|
342
|
+
|
343
|
+
builder = new actn.Builder(_schema_name, _table_name, search_path, {where: _cond})
|
344
|
+
|
345
|
+
[sql,params] = builder.build_update(_data)
|
346
|
+
|
347
|
+
rows = plv8.execute(sql,params)
|
348
|
+
result = _.pluck(rows,'data')
|
349
|
+
result = result[0] if result.length is 1
|
350
|
+
|
351
|
+
builder = null
|
352
|
+
|
353
|
+
return JSON.stringify(result)
|
354
|
+
|
355
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
356
|
+
|
357
|
+
|
358
|
+
|
359
|
+
|
360
|
+
|
361
|
+
-- ##
|
362
|
+
-- # Delete single row by uuid
|
363
|
+
-- # SELECT remove('users',uuid-1234567);
|
364
|
+
|
365
|
+
CREATE or REPLACE FUNCTION __delete(_schema_name text, _table_name text, _cond json) RETURNS json AS $$
|
366
|
+
|
367
|
+
search_path = if _schema_name is "public" then _schema_name else "#{_schema_name},public"
|
368
|
+
|
369
|
+
builder = new actn.Builder(_schema_name, _table_name, search_path, {where: _cond})
|
370
|
+
|
371
|
+
[sql,params] = builder.build_delete()
|
372
|
+
|
373
|
+
# plv8.elog(NOTICE,"DELETE",sql,params)
|
374
|
+
|
375
|
+
rows = plv8.execute(sql,params)
|
376
|
+
result = _.pluck(rows,'data')
|
377
|
+
result = result[0] if result.length is 1
|
378
|
+
|
379
|
+
builder = null
|
380
|
+
|
381
|
+
return JSON.stringify(result)
|
382
|
+
|
383
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
384
|
+
|
385
|
+
|
386
|
+
|
387
|
+
|
388
|
+
|
389
|
+
|
390
|
+
|
391
|
+
-- ##
|
392
|
+
-- # Validate data by json schema
|
393
|
+
-- # SELECT validate(model_name, data);
|
394
|
+
|
395
|
+
CREATE or REPLACE FUNCTION __validate(_name text, _data json) RETURNS json AS $$
|
396
|
+
|
397
|
+
data = _data
|
398
|
+
|
399
|
+
# plv8.elog(NOTICE,"__VALIDATE",_name,JSON.stringify(_data))
|
400
|
+
|
401
|
+
return data unless model = plv8.find_function('__find_model')(_name)
|
402
|
+
|
403
|
+
model = JSON.parse(model)
|
404
|
+
|
405
|
+
# plv8.elog(NOTICE,"__VALIDATE MODEL",_name,JSON.stringify(model))
|
406
|
+
|
407
|
+
if model?.schema?
|
408
|
+
|
409
|
+
errors = actn.jjv.validate(model.schema,data)
|
410
|
+
|
411
|
+
plv8.elog(NOTICE,"VALVAL",JSON.stringify(model.schema))
|
412
|
+
|
413
|
+
if data.uuid? and model.schema.readonly_attributes?
|
414
|
+
|
415
|
+
data = _.omit(data,model.schema.readonly_attributes)
|
416
|
+
|
417
|
+
# plv8.elog(NOTICE,"VALIDATE READONLY",JSON.stringify(data),JSON.stringify(model.schema.readonly_attributes))
|
418
|
+
|
419
|
+
|
420
|
+
else if model.schema.unique_attributes?
|
421
|
+
|
422
|
+
_schema = if _name is "Model" then "core" else "public"
|
423
|
+
_table = model.name.tableize()
|
424
|
+
__query = plv8.find_function("__query")
|
425
|
+
|
426
|
+
for uniq_attr in model.schema.unique_attributes or []
|
427
|
+
if data[uniq_attr]?
|
428
|
+
where = {}
|
429
|
+
where[uniq_attr] = data[uniq_attr]
|
430
|
+
# plv8.elog(NOTICE,"VALIDATE WHERE",JSON.stringify({where: where}))
|
431
|
+
found = JSON.parse(__query(_schema,_table,{where: where}))
|
432
|
+
# plv8.elog(NOTICE,"VALIDATE FOUND",JSON.stringify(found))
|
433
|
+
unless _.isEmpty(found)
|
434
|
+
errors ?= {validation: {}}
|
435
|
+
errors['validation'][uniq_attr] ?= {}
|
436
|
+
errors['validation'][uniq_attr]["has already been taken"] = true
|
437
|
+
|
438
|
+
data = {errors: errors} if errors?
|
439
|
+
|
440
|
+
# plv8.elog(NOTICE,"__VALIDATE DATA",_name,JSON.stringify(data))
|
441
|
+
|
442
|
+
return data
|
443
|
+
|
444
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
445
|
+
|
446
|
+
|
447
|
+
|
448
|
+
|
449
|
+
|
450
|
+
-- ##
|
451
|
+
-- # finds model with given name
|
452
|
+
-- # SELECT __find_model(model_name);
|
453
|
+
|
454
|
+
CREATE or REPLACE FUNCTION __find_model(_name text) RETURNS json AS $$
|
455
|
+
|
456
|
+
rows = plv8.execute("""SET search_path TO core,public;
|
457
|
+
SELECT data FROM core.models
|
458
|
+
WHERE __string(data,'name'::text) = $1::text""", [_name])
|
459
|
+
|
460
|
+
return unless rows?
|
461
|
+
|
462
|
+
result = _.pluck(rows,'data')[0]
|
463
|
+
|
464
|
+
return JSON.stringify(result)
|
465
|
+
|
466
|
+
$$ LANGUAGE plcoffee STABLE STRICT;
|
467
|
+
|
468
|
+
|
469
|
+
|
470
|
+
|
471
|
+
|
472
|
+
|