syntropy 0.41.0 → 0.42.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/TODO.md +334 -31
- data/cmd/new/template/app/_lib/storage.rb +2 -2
- data/cmd/new/template/config/test.rb +1 -1
- data/examples/agent/config/test.rb +1 -1
- data/examples/blog/app/_lib/posts.rb +10 -6
- data/examples/blog/app/_lib/storage.rb +2 -2
- data/examples/blog/app/posts/[id]/index.rb +2 -2
- data/examples/blog/config/test.rb +1 -1
- data/lib/syntropy/collection.rb +206 -0
- data/lib/syntropy/connection_pool.rb +118 -0
- data/lib/syntropy/controller_extensions.rb +12 -0
- data/lib/syntropy/http/server_connection.rb +15 -28
- data/lib/syntropy/json_api.rb +3 -12
- data/lib/syntropy/kv_store.rb +57 -0
- data/lib/syntropy/logger.rb +24 -47
- data/lib/syntropy/markdown.rb +1 -1
- data/lib/syntropy/module_loader.rb +53 -14
- data/lib/syntropy/schema.rb +95 -0
- data/lib/syntropy/storage.rb +42 -4
- data/lib/syntropy/test.rb +1 -1
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +2 -1
- data/syntropy.gemspec +2 -2
- data/test/fixtures/app/_lib/dep_invalidate.rb +5 -0
- data/test/fixtures/collection/basic/_articles/2026-06-30-foo.md +6 -0
- data/test/fixtures/collection/basic/_articles/2026-07-13-bar.md +6 -0
- data/test/fixtures/collection/basic/_articles/2026-08-07-baz.md +6 -0
- data/test/fixtures/collection/basic/index+.rb +23 -0
- data/test/fixtures/collection/docs/_pages/01-foo/01-foo.md +4 -0
- data/test/fixtures/collection/docs/_pages/01-foo/02-bar.md +4 -0
- data/test/fixtures/collection/docs/_pages/01-foo/_index.yml +1 -0
- data/test/fixtures/collection/docs/_pages/02-bar/01-bar.md +4 -0
- data/test/fixtures/collection/docs/_pages/02-bar/02-baz.md +4 -0
- data/test/fixtures/collection/docs/_pages/02-bar/_index.yml +1 -0
- data/test/fixtures/collection/docs/index+.rb +16 -0
- data/test/test_app.rb +7 -7
- data/test/test_cmd_new.rb +38 -0
- data/test/test_collection.rb +164 -0
- data/test/test_connection_pool.rb +79 -3
- data/test/test_http_server_connection.rb +6 -6
- data/test/test_json_api.rb +4 -4
- data/test/test_kv_store.rb +6 -31
- data/test/test_markdown.rb +3 -3
- data/test/test_module_loader.rb +15 -0
- data/test/test_schema.rb +4 -4
- data/test/test_storage.rb +103 -0
- metadata +24 -11
- data/lib/syntropy/storage/connection_pool.rb +0 -106
- data/lib/syntropy/storage/kv_store.rb +0 -68
- data/lib/syntropy/storage/prepared_query.rb +0 -36
- data/lib/syntropy/storage/schema.rb +0 -92
- data/lib/syntropy/storage/store.rb +0 -33
- data/test/test_store.rb +0 -24
|
@@ -6,12 +6,13 @@ class ConnectionPoolTest < Minitest::Test
|
|
|
6
6
|
def setup
|
|
7
7
|
@machine = UM.new
|
|
8
8
|
@fn = "/tmp/#{rand(100000)}.db"
|
|
9
|
-
@cp = Syntropy::
|
|
9
|
+
@cp = Syntropy::ConnectionPool.new(@machine, @fn, 4)
|
|
10
10
|
|
|
11
11
|
FileUtils.rm(@fn) rescue nil
|
|
12
12
|
@standalone_db = Extralite::Database.new(@fn)
|
|
13
|
-
@standalone_db.execute("create table foo (x,y, z)")
|
|
13
|
+
@standalone_db.execute("create table foo (x, y, z)")
|
|
14
14
|
@standalone_db.execute("insert into foo values (1, 2, 3)")
|
|
15
|
+
@standalone_db.execute("insert into foo values (4, 5, 6)")
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def teardown
|
|
@@ -26,7 +27,7 @@ class ConnectionPoolTest < Minitest::Test
|
|
|
26
27
|
assert_kind_of Extralite::Database, db
|
|
27
28
|
|
|
28
29
|
records = db.query("select * from foo")
|
|
29
|
-
assert_equal [{x: 1, y: 2, z: 3}], records
|
|
30
|
+
assert_equal [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}], records
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
assert_equal 1, @cp.count
|
|
@@ -52,6 +53,7 @@ class ConnectionPoolTest < Minitest::Test
|
|
|
52
53
|
records = @standalone_db.query("select * from foo order by x")
|
|
53
54
|
assert_equal [
|
|
54
55
|
{x: 1, y: 2, z: 3},
|
|
56
|
+
{x: 4, y: 5, z: 6},
|
|
55
57
|
{x: 11, y: 12, z: 13},
|
|
56
58
|
{x: 21, y: 22, z: 23},
|
|
57
59
|
], records
|
|
@@ -83,4 +85,78 @@ class ConnectionPoolTest < Minitest::Test
|
|
|
83
85
|
|
|
84
86
|
assert_equal 1, dbs.uniq.size
|
|
85
87
|
end
|
|
88
|
+
|
|
89
|
+
def test_connection_pool_query
|
|
90
|
+
rows = @cp.query('select * from foo')
|
|
91
|
+
assert_equal [{x: 1, y: 2, z: 3}, {x: 4, y: 5, z: 6}], rows
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def test_connection_pool_query_params
|
|
95
|
+
rows = @cp.query('select * from foo where y = ?', 5)
|
|
96
|
+
assert_equal [{x: 4, y: 5, z: 6}], rows
|
|
97
|
+
|
|
98
|
+
rows = @cp.query('select * from foo where y = ?', 55)
|
|
99
|
+
assert_equal [], rows
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_connection_pool_query_transform_params
|
|
103
|
+
t = ->(r) { r[:z] }
|
|
104
|
+
|
|
105
|
+
rows = @cp.query(t, 'select * from foo where y <= ?', 5)
|
|
106
|
+
assert_equal [3, 6], rows
|
|
107
|
+
|
|
108
|
+
rows = @cp.query(t, 'select * from foo where y = ?', 55)
|
|
109
|
+
assert_equal [], rows
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_connection_pool_query_single_row
|
|
113
|
+
row = @cp.query_single_row('select * from foo where x = 1')
|
|
114
|
+
assert_equal({x: 1, y: 2, z: 3}, row)
|
|
115
|
+
|
|
116
|
+
row = @cp.query_single_row('select * from foo where x = 3')
|
|
117
|
+
assert_nil row
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_connection_pool_query_single_row_params
|
|
121
|
+
row = @cp.query_single_row('select * from foo where x = ?', 1)
|
|
122
|
+
assert_equal({x: 1, y: 2, z: 3}, row)
|
|
123
|
+
|
|
124
|
+
row = @cp.query_single_row('select * from foo where x = :x', x: 4)
|
|
125
|
+
assert_equal({x: 4, y: 5, z: 6}, row)
|
|
126
|
+
|
|
127
|
+
row = @cp.query_single_row('select * from foo where x = ?', 42)
|
|
128
|
+
assert_nil row
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def test_connection_pool_query_single_row_transform_params
|
|
132
|
+
t = ->(r) { r[:z] }
|
|
133
|
+
|
|
134
|
+
row = @cp.query_single_row(t, 'select * from foo where x = ?', 1)
|
|
135
|
+
assert_equal(3, row)
|
|
136
|
+
|
|
137
|
+
row = @cp.query_single_row(t, 'select * from foo where x = :x', x: 4)
|
|
138
|
+
assert_equal(6, row)
|
|
139
|
+
|
|
140
|
+
row = @cp.query_single_row(t, 'select * from foo where x = ?', 42)
|
|
141
|
+
assert_nil row
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_connection_pool_query_single_value
|
|
145
|
+
v = @cp.query_single_value('select y from foo where x = 1')
|
|
146
|
+
assert_equal 2, v
|
|
147
|
+
|
|
148
|
+
v = @cp.query_single_value('select * from foo where x = 3')
|
|
149
|
+
assert_nil v
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def test_connection_pool_query_single_value_params
|
|
153
|
+
v = @cp.query_single_value('select y from foo where x = ?', 1)
|
|
154
|
+
assert_equal 2, v
|
|
155
|
+
|
|
156
|
+
v = @cp.query_single_value('select y from foo where x = :x', x: 4)
|
|
157
|
+
assert_equal 5, v
|
|
158
|
+
|
|
159
|
+
v = @cp.query_single_value('select * from foo where x = ?', 3)
|
|
160
|
+
assert_nil v
|
|
161
|
+
end
|
|
86
162
|
end
|
|
@@ -463,7 +463,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
463
463
|
response << data
|
|
464
464
|
count += 1
|
|
465
465
|
@machine.snooze
|
|
466
|
-
break if data[-
|
|
466
|
+
break if data[-5..-1] == "0\r\n\r\n"
|
|
467
467
|
end
|
|
468
468
|
|
|
469
469
|
chunks = "#{chunk_size.to_s(16)}\r\n#{'*' * chunk_size}\r\n" * chunk_count
|
|
@@ -478,7 +478,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
478
478
|
IO.write(fn, 'foobar')
|
|
479
479
|
|
|
480
480
|
@hook = ->(req) do
|
|
481
|
-
req.respond_with_static_file(fn, nil, nil,
|
|
481
|
+
req.respond_with_static_file(fn, nil, nil, {})
|
|
482
482
|
rescue StandardError => e
|
|
483
483
|
p e
|
|
484
484
|
p e.backtrace
|
|
@@ -499,9 +499,9 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
499
499
|
response << data
|
|
500
500
|
count += 1
|
|
501
501
|
@machine.snooze
|
|
502
|
-
break if data[-
|
|
502
|
+
break if data[-5..-1] == "0\r\n\r\n"
|
|
503
503
|
end
|
|
504
|
-
|
|
504
|
+
|
|
505
505
|
content = IO.read(fn)
|
|
506
506
|
file_size = content.bytesize
|
|
507
507
|
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n#{file_size.to_s(16)}\r\n#{content}\r\n0\r\n\r\n"
|
|
@@ -514,7 +514,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
514
514
|
IO.write(fn, 'foobar')
|
|
515
515
|
|
|
516
516
|
@hook = ->(req) do
|
|
517
|
-
req.respond_with_static_file(fn, nil, nil, {
|
|
517
|
+
req.respond_with_static_file(fn, nil, nil, {})
|
|
518
518
|
req.adapter.close
|
|
519
519
|
end
|
|
520
520
|
|
|
@@ -530,7 +530,7 @@ class HTTPServerConnectionTest < Minitest::Test
|
|
|
530
530
|
@machine.snooze
|
|
531
531
|
end
|
|
532
532
|
|
|
533
|
-
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\
|
|
533
|
+
expected = "HTTP/1.1 200\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nfoobar\r\n0\r\n\r\n"
|
|
534
534
|
assert_equal expected, response
|
|
535
535
|
end
|
|
536
536
|
|
data/test/test_json_api.rb
CHANGED
|
@@ -28,7 +28,7 @@ class JSONAPITest < Minitest::Test
|
|
|
28
28
|
req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
|
|
29
29
|
@app.call(req)
|
|
30
30
|
assert_equal HTTP::OK, req.response_status
|
|
31
|
-
assert_equal({
|
|
31
|
+
assert_equal({ status: 'OK', response: nil }, req.response_json)
|
|
32
32
|
|
|
33
33
|
req = mock_req(':method' => 'POST', ':path' => '/?q=foo')
|
|
34
34
|
@app.call(req)
|
|
@@ -38,7 +38,7 @@ class JSONAPITest < Minitest::Test
|
|
|
38
38
|
req = mock_req(':method' => 'POST', ':path' => '/?q=bar&v=foo')
|
|
39
39
|
@app.call(req)
|
|
40
40
|
assert_equal HTTP::OK, req.response_status
|
|
41
|
-
assert_equal({
|
|
41
|
+
assert_equal({ status: 'OK', response: true }, req.response_json)
|
|
42
42
|
|
|
43
43
|
req = mock_req(':method' => 'GET', ':path' => '/?q=bar&v=foo')
|
|
44
44
|
@app.call(req)
|
|
@@ -47,12 +47,12 @@ class JSONAPITest < Minitest::Test
|
|
|
47
47
|
req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
|
|
48
48
|
@app.call(req)
|
|
49
49
|
assert_equal HTTP::OK, req.response_status
|
|
50
|
-
assert_equal({
|
|
50
|
+
assert_equal({ status: 'OK', response: 'foo' }, req.response_json)
|
|
51
51
|
|
|
52
52
|
req = mock_req(':method' => 'GET', ':path' => '/?q=foo')
|
|
53
53
|
@app.call(req)
|
|
54
54
|
assert_equal HTTP::OK, req.response_status
|
|
55
|
-
assert_equal({
|
|
55
|
+
assert_equal({ status: 'OK', response: 'foo' }, req.response_json)
|
|
56
56
|
|
|
57
57
|
req = mock_req(':method' => 'GET', ':path' => '/?q=xxx')
|
|
58
58
|
@app.call(req)
|
data/test/test_kv_store.rb
CHANGED
|
@@ -1,51 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'helper'
|
|
4
|
-
require 'syntropy/storage/kv_store'
|
|
5
4
|
|
|
6
5
|
class KVStoreTest < Minitest::Test
|
|
7
6
|
def setup
|
|
8
7
|
@machine = UM.new
|
|
9
|
-
@fn = "/tmp/#{
|
|
8
|
+
@fn = "/tmp/#{SecureRandom.hex(8)}.db"
|
|
10
9
|
FileUtils.rm(@fn) rescue nil
|
|
11
|
-
@cp = Syntropy::
|
|
10
|
+
@cp = Syntropy::ConnectionPool.new(@machine, @fn, 4)
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def teardown
|
|
15
|
-
@cp
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def test_connection_pool_prepare
|
|
19
|
-
pq = Syntropy::Storage.prepare('select ? as a, 42 as b')
|
|
20
|
-
assert_kind_of Syntropy::Storage::PreparedQuery, pq
|
|
21
|
-
assert_equal 'select ? as a, 42 as b', pq.sql
|
|
22
|
-
assert_equal :prepare, pq.mode
|
|
23
|
-
|
|
24
|
-
assert_kind_of Extralite::Query, @cp.with_db { it[pq] }
|
|
25
|
-
assert_equal [{ a: 'foo', b: 42 }], @cp.with_db { it[pq].bind('foo').to_a }
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def test_connection_pool_prepare_splat
|
|
29
|
-
pq = Syntropy::Storage.prepare_splat('select ?')
|
|
30
|
-
assert_kind_of Syntropy::Storage::PreparedQuery, pq
|
|
31
|
-
assert_equal 'select ?', pq.sql
|
|
32
|
-
assert_equal :prepare_splat, pq.mode
|
|
33
|
-
|
|
34
|
-
assert_kind_of Extralite::Query, @cp.with_db { it[pq] }
|
|
35
|
-
assert_equal ['foo'], @cp.with_db { it[pq].bind('foo').to_a }
|
|
14
|
+
@cp&.close
|
|
36
15
|
end
|
|
37
16
|
|
|
38
17
|
def test_kv_store_apply_schema
|
|
39
|
-
assert_respond_to Syntropy::Storage::KVStore, :apply_schema
|
|
40
|
-
|
|
41
18
|
assert_raises(Extralite::SQLError) { @cp.query('select * from kv') }
|
|
42
|
-
Syntropy::
|
|
19
|
+
Syntropy::KVStore.new(@cp, 'kv')
|
|
43
20
|
assert_equal [], @cp.query('select * from kv')
|
|
44
21
|
end
|
|
45
22
|
|
|
46
23
|
def test_kv_store_get_set
|
|
47
|
-
Syntropy::
|
|
48
|
-
kv_store = Syntropy::Storage::KVStore.new(@cp, 'kv')
|
|
24
|
+
kv_store = Syntropy::KVStore.new(@cp, 'kv')
|
|
49
25
|
|
|
50
26
|
@cp.with_db do |db|
|
|
51
27
|
assert_nil kv_store.get(db, 'foo')
|
|
@@ -63,8 +39,7 @@ class KVStoreTest < Minitest::Test
|
|
|
63
39
|
end
|
|
64
40
|
|
|
65
41
|
def test_kv_store_setex_sweep
|
|
66
|
-
Syntropy::
|
|
67
|
-
kv_store = Syntropy::Storage::KVStore.new(@cp, 'kv')
|
|
42
|
+
kv_store = Syntropy::KVStore.new(@cp, 'kv')
|
|
68
43
|
|
|
69
44
|
@cp.with_db do |db|
|
|
70
45
|
kv_store.set(db, 'foo', '123')
|
data/test/test_markdown.rb
CHANGED
|
@@ -17,7 +17,7 @@ class MarkdownParseTest < Minitest::Test
|
|
|
17
17
|
|
|
18
18
|
assert_equal h, atts
|
|
19
19
|
assert_equal({ foo: 'bar' }, atts)
|
|
20
|
-
assert_equal "foo *bar
|
|
20
|
+
assert_equal "foo *bar*", md2
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def test_markdown_parse_file
|
|
@@ -38,13 +38,13 @@ class MarkdownParseTest < Minitest::Test
|
|
|
38
38
|
atts, md = Syntropy::Markdown.parse_file(path, env)
|
|
39
39
|
assert_equal "/tmp/test/#{name}", atts[:url]
|
|
40
40
|
assert_equal 'baz', atts[:bar]
|
|
41
|
-
assert_equal "bar *baz
|
|
41
|
+
assert_equal "bar *baz*", md
|
|
42
42
|
|
|
43
43
|
env = { app_root: '/tmp' }
|
|
44
44
|
atts, md = Syntropy::Markdown.parse_file(path, env)
|
|
45
45
|
assert_equal "/test/#{name}", atts[:url]
|
|
46
46
|
assert_equal 'baz', atts[:bar]
|
|
47
|
-
assert_equal "bar *baz
|
|
47
|
+
assert_equal "bar *baz*", md
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
|
data/test/test_module_loader.rb
CHANGED
|
@@ -74,6 +74,20 @@ class ModuleLoaderTest < Minitest::Test
|
|
|
74
74
|
assert_equal [], @loader.modules.keys
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def test_invalidate_on_file_change
|
|
78
|
+
_mod = @loader.load('_lib/dep_invalidate')
|
|
79
|
+
assert_equal ['/_lib/self', '/_lib/dep_invalidate'], @loader.modules.keys
|
|
80
|
+
|
|
81
|
+
@loader.invalidate_fn(File.join(@loader.app_root, '_lib/circular/a'))
|
|
82
|
+
assert_equal ['/_lib/self'], @loader.modules.keys
|
|
83
|
+
|
|
84
|
+
_mod = @loader.load('_lib/dep_invalidate')
|
|
85
|
+
assert_equal ['/_lib/self', '/_lib/dep_invalidate'], @loader.modules.keys
|
|
86
|
+
|
|
87
|
+
@loader.invalidate_fn(File.join(@loader.app_root, '_lib/circular/x/y/z'))
|
|
88
|
+
assert_equal ['/_lib/self'], @loader.modules.keys
|
|
89
|
+
end
|
|
90
|
+
|
|
77
91
|
def test_index_module_env
|
|
78
92
|
mod = @loader.load('mod/bar/index+')
|
|
79
93
|
assert_equal '/mod/bar', mod.env[:ref]
|
|
@@ -90,6 +104,7 @@ class ModuleLoaderTest < Minitest::Test
|
|
|
90
104
|
assert_equal [
|
|
91
105
|
'_lib/callable',
|
|
92
106
|
'_lib/dep',
|
|
107
|
+
'_lib/dep_invalidate',
|
|
93
108
|
'_lib/env',
|
|
94
109
|
'_lib/klass',
|
|
95
110
|
'_lib/missing-export',
|
data/test/test_schema.rb
CHANGED
|
@@ -7,7 +7,7 @@ class SchemaTest < Minitest::Test
|
|
|
7
7
|
@machine = UM.new
|
|
8
8
|
@fn = "/tmp/#{rand(100000)}.db"
|
|
9
9
|
FileUtils.rm(@fn) rescue nil
|
|
10
|
-
@cp = Syntropy::
|
|
10
|
+
@cp = Syntropy::ConnectionPool.new(@machine, @fn, 4)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def teardown
|
|
@@ -15,7 +15,7 @@ class SchemaTest < Minitest::Test
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def test_db_schema_initial
|
|
18
|
-
schema = Syntropy::
|
|
18
|
+
schema = Syntropy::Schema.new do
|
|
19
19
|
initial do |db|
|
|
20
20
|
db.execute <<~SQL
|
|
21
21
|
create table posts (
|
|
@@ -35,7 +35,7 @@ class SchemaTest < Minitest::Test
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def test_db_schema_version_blocks
|
|
38
|
-
schema = Syntropy::
|
|
38
|
+
schema = Syntropy::Schema.new do
|
|
39
39
|
initial do |db|
|
|
40
40
|
db.execute <<~SQL
|
|
41
41
|
create table posts (
|
|
@@ -80,7 +80,7 @@ class SchemaTest < Minitest::Test
|
|
|
80
80
|
machine: @machine,
|
|
81
81
|
app_root: File.join(__dir__, 'fixtures/schema')
|
|
82
82
|
})
|
|
83
|
-
schema = Syntropy::
|
|
83
|
+
schema = Syntropy::Schema.new(module_loader:, schema_root: '/')
|
|
84
84
|
|
|
85
85
|
assert_nil schema.current_version(@cp)
|
|
86
86
|
schema.apply(@cp)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'helper'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
|
|
6
|
+
class StorageTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@machine = UM.new
|
|
9
|
+
@fn = "/tmp/#{SecureRandom.hex(8)}.db"
|
|
10
|
+
|
|
11
|
+
@module_loader = Syntropy::ModuleLoader.new({
|
|
12
|
+
machine: @machine,
|
|
13
|
+
app_root: File.join(__dir__, 'fixtures/schema')
|
|
14
|
+
})
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_storage_missing_config
|
|
18
|
+
config = {}
|
|
19
|
+
assert_raises(Syntropy::Error) {
|
|
20
|
+
Syntropy::Storage.new(
|
|
21
|
+
@machine,
|
|
22
|
+
@module_loader,
|
|
23
|
+
config
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_storage_connection_pool
|
|
29
|
+
config = {
|
|
30
|
+
path: @fn
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
storage = Syntropy::Storage.new(
|
|
34
|
+
@machine,
|
|
35
|
+
@module_loader,
|
|
36
|
+
config
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
cp = storage.connection_pool
|
|
40
|
+
assert_kind_of Syntropy::ConnectionPool, cp
|
|
41
|
+
|
|
42
|
+
db = Extralite::Database.new(@fn)
|
|
43
|
+
|
|
44
|
+
cp.with_db {
|
|
45
|
+
it.execute('create table x (y); insert into x (y) values (42);')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
assert_equal 42, db.query_single_splat('select y from x')
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def test_storage_schema
|
|
52
|
+
config = {
|
|
53
|
+
path: @fn,
|
|
54
|
+
schema_root: '/'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
storage = Syntropy::Storage.new(
|
|
58
|
+
@machine,
|
|
59
|
+
@module_loader,
|
|
60
|
+
config
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
schema = storage.schema
|
|
64
|
+
assert_kind_of Syntropy::Schema, schema
|
|
65
|
+
|
|
66
|
+
cp = storage.connection_pool
|
|
67
|
+
assert_nil schema.current_version(cp)
|
|
68
|
+
schema.apply(cp)
|
|
69
|
+
assert_equal '2026-05-30-bar', schema.current_version(cp)
|
|
70
|
+
|
|
71
|
+
assert_equal [
|
|
72
|
+
{
|
|
73
|
+
id: 1,
|
|
74
|
+
title: 'foo',
|
|
75
|
+
body: 'baz'
|
|
76
|
+
}
|
|
77
|
+
], cp.query('select id, title, body from posts')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_storage_query
|
|
81
|
+
config = { path: @fn }
|
|
82
|
+
storage = Syntropy::Storage.new(
|
|
83
|
+
@machine,
|
|
84
|
+
@module_loader,
|
|
85
|
+
config
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
r = storage.query('select :foo as a, 42 as b', foo: 'bar')
|
|
89
|
+
assert_equal [{ a: 'bar', b: 42 }], r
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_connection_pool_prepare_splat
|
|
93
|
+
# pq = Syntropy::Storage.prepare_splat('select ?')
|
|
94
|
+
# assert_kind_of Syntropy::PreparedQuery, pq
|
|
95
|
+
# assert_equal 'select ?', pq.sql
|
|
96
|
+
# assert_equal :prepare_splat, pq.mode
|
|
97
|
+
|
|
98
|
+
# assert_kind_of Extralite::Query, @cp.with_db { it[pq] }
|
|
99
|
+
# assert_equal ['foo'], @cp.with_db { it[pq].bind('foo').to_a }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: syntropy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.42.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sharon Rosner
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 3.
|
|
18
|
+
version: 3.1.3
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 3.
|
|
25
|
+
version: 3.1.3
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: papercraft
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - "~>"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 1.0.
|
|
46
|
+
version: 1.0.6
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 1.0.
|
|
53
|
+
version: 1.0.6
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: json
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -270,6 +270,8 @@ files:
|
|
|
270
270
|
- lib/syntropy/applets/builtin/json_api.js
|
|
271
271
|
- lib/syntropy/applets/builtin/ping.rb
|
|
272
272
|
- lib/syntropy/applets/builtin/req.rb
|
|
273
|
+
- lib/syntropy/collection.rb
|
|
274
|
+
- lib/syntropy/connection_pool.rb
|
|
273
275
|
- lib/syntropy/controller_extensions.rb
|
|
274
276
|
- lib/syntropy/dev_mode.rb
|
|
275
277
|
- lib/syntropy/errors.rb
|
|
@@ -281,6 +283,7 @@ files:
|
|
|
281
283
|
- lib/syntropy/http/server_connection.rb
|
|
282
284
|
- lib/syntropy/http/status.rb
|
|
283
285
|
- lib/syntropy/json_api.rb
|
|
286
|
+
- lib/syntropy/kv_store.rb
|
|
284
287
|
- lib/syntropy/logger.rb
|
|
285
288
|
- lib/syntropy/markdown.rb
|
|
286
289
|
- lib/syntropy/mime_types.rb
|
|
@@ -292,14 +295,10 @@ files:
|
|
|
292
295
|
- lib/syntropy/request/response.rb
|
|
293
296
|
- lib/syntropy/request/validation.rb
|
|
294
297
|
- lib/syntropy/routing_tree.rb
|
|
298
|
+
- lib/syntropy/schema.rb
|
|
295
299
|
- lib/syntropy/session.rb
|
|
296
300
|
- lib/syntropy/side_run.rb
|
|
297
301
|
- lib/syntropy/storage.rb
|
|
298
|
-
- lib/syntropy/storage/connection_pool.rb
|
|
299
|
-
- lib/syntropy/storage/kv_store.rb
|
|
300
|
-
- lib/syntropy/storage/prepared_query.rb
|
|
301
|
-
- lib/syntropy/storage/schema.rb
|
|
302
|
-
- lib/syntropy/storage/store.rb
|
|
303
302
|
- lib/syntropy/test.rb
|
|
304
303
|
- lib/syntropy/version.rb
|
|
305
304
|
- syntropy.gemspec
|
|
@@ -314,6 +313,7 @@ files:
|
|
|
314
313
|
- test/fixtures/app/_lib/circular/b.rb
|
|
315
314
|
- test/fixtures/app/_lib/circular/c.rb
|
|
316
315
|
- test/fixtures/app/_lib/dep.rb
|
|
316
|
+
- test/fixtures/app/_lib/dep_invalidate.rb
|
|
317
317
|
- test/fixtures/app/_lib/env.rb
|
|
318
318
|
- test/fixtures/app/_lib/klass.rb
|
|
319
319
|
- test/fixtures/app/_lib/missing-export.rb
|
|
@@ -366,6 +366,17 @@ files:
|
|
|
366
366
|
- test/fixtures/app_setup/index.rb
|
|
367
367
|
- test/fixtures/app_with_schema/_schema/2026-01-02-foo.rb
|
|
368
368
|
- test/fixtures/app_with_schema/_schema/2026-05-30-bar.rb
|
|
369
|
+
- test/fixtures/collection/basic/_articles/2026-06-30-foo.md
|
|
370
|
+
- test/fixtures/collection/basic/_articles/2026-07-13-bar.md
|
|
371
|
+
- test/fixtures/collection/basic/_articles/2026-08-07-baz.md
|
|
372
|
+
- test/fixtures/collection/basic/index+.rb
|
|
373
|
+
- test/fixtures/collection/docs/_pages/01-foo/01-foo.md
|
|
374
|
+
- test/fixtures/collection/docs/_pages/01-foo/02-bar.md
|
|
375
|
+
- test/fixtures/collection/docs/_pages/01-foo/_index.yml
|
|
376
|
+
- test/fixtures/collection/docs/_pages/02-bar/01-bar.md
|
|
377
|
+
- test/fixtures/collection/docs/_pages/02-bar/02-baz.md
|
|
378
|
+
- test/fixtures/collection/docs/_pages/02-bar/_index.yml
|
|
379
|
+
- test/fixtures/collection/docs/index+.rb
|
|
369
380
|
- test/fixtures/controllers/by_host/bar.com/index.rb
|
|
370
381
|
- test/fixtures/controllers/by_host/foo.com/index.rb
|
|
371
382
|
- test/fixtures/controllers/by_host_dir.rb
|
|
@@ -379,6 +390,8 @@ files:
|
|
|
379
390
|
- test/run.rb
|
|
380
391
|
- test/test_app.rb
|
|
381
392
|
- test/test_caching.rb
|
|
393
|
+
- test/test_cmd_new.rb
|
|
394
|
+
- test/test_collection.rb
|
|
382
395
|
- test/test_connection_pool.rb
|
|
383
396
|
- test/test_controller.rb
|
|
384
397
|
- test/test_errors.rb
|
|
@@ -398,7 +411,7 @@ files:
|
|
|
398
411
|
- test/test_schema.rb
|
|
399
412
|
- test/test_server.rb
|
|
400
413
|
- test/test_side_run.rb
|
|
401
|
-
- test/
|
|
414
|
+
- test/test_storage.rb
|
|
402
415
|
- test/test_test.rb
|
|
403
416
|
homepage: https://github.com/digital-fabric/syntropy
|
|
404
417
|
licenses:
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'extralite'
|
|
4
|
-
|
|
5
|
-
module Syntropy
|
|
6
|
-
module Storage
|
|
7
|
-
# ConnectionPool implements concurrent access to an SQLite database.
|
|
8
|
-
class ConnectionPool
|
|
9
|
-
attr_reader :count
|
|
10
|
-
|
|
11
|
-
# Initializes the connection pool.
|
|
12
|
-
#
|
|
13
|
-
# @param machine [UringMachine] machine instance
|
|
14
|
-
# @param fn [String] database filename
|
|
15
|
-
# @param max_conn [Integer] maximum number of connections
|
|
16
|
-
# @return [void]
|
|
17
|
-
def initialize(machine, fn, max_conn)
|
|
18
|
-
@machine = machine
|
|
19
|
-
@fn = fn
|
|
20
|
-
@count = 0
|
|
21
|
-
@max_conn = max_conn
|
|
22
|
-
@queue = UM::Queue.new
|
|
23
|
-
@key = :"connection_pool_#{object_id}"
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Checks out a database connection, passing it to the given block. This
|
|
27
|
-
# method is reentrant - called from the same fiber, it will yield the same
|
|
28
|
-
# database connection.
|
|
29
|
-
#
|
|
30
|
-
# @return [Extralite::Database] database connection
|
|
31
|
-
def with_db
|
|
32
|
-
if (db = Thread.current[@key])
|
|
33
|
-
@machine.snooze
|
|
34
|
-
return yield(db)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
db = checkout
|
|
38
|
-
begin
|
|
39
|
-
Thread.current[@key] = db
|
|
40
|
-
yield(db)
|
|
41
|
-
ensure
|
|
42
|
-
Thread.current[@key] = nil
|
|
43
|
-
checkin(db)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Performs the given query by acquiring a database connection and running
|
|
48
|
-
# the query.
|
|
49
|
-
#
|
|
50
|
-
# @param sql [String] SQL query
|
|
51
|
-
# @return [Array<Hash>] result rows
|
|
52
|
-
def query(sql, *, **, &)
|
|
53
|
-
with_db { it.query(sql, *, **, &) }
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Executes the given query by acquiring a database connection and running
|
|
57
|
-
# the query.
|
|
58
|
-
#
|
|
59
|
-
# @param sql [String] SQL query
|
|
60
|
-
# @return [Integer] number of changed rows
|
|
61
|
-
def execute(sql, *, **)
|
|
62
|
-
with_db { it.execute(sql, *, **) }
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Closes the connection pool by removing all connections.
|
|
66
|
-
#
|
|
67
|
-
# @return [void]
|
|
68
|
-
def close
|
|
69
|
-
while @queue.count > 0
|
|
70
|
-
db = @machine.shift(@queue)
|
|
71
|
-
db.close
|
|
72
|
-
@count -= 1
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
private
|
|
77
|
-
|
|
78
|
-
# Checks out a connection from the pool.
|
|
79
|
-
#
|
|
80
|
-
# @return [Extralite::Database] database connection
|
|
81
|
-
def checkout
|
|
82
|
-
return make_db_instance if @queue.count == 0 && @count < @max_conn
|
|
83
|
-
|
|
84
|
-
@machine.shift(@queue)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Checks in a connection to the pool.
|
|
88
|
-
#
|
|
89
|
-
# @param db [Extralite::Database] database connection
|
|
90
|
-
# @return [void]
|
|
91
|
-
def checkin(db)
|
|
92
|
-
@machine.push(@queue, db)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# Creates a database connection.
|
|
96
|
-
#
|
|
97
|
-
# @return [Extralite::Database] database connection
|
|
98
|
-
def make_db_instance
|
|
99
|
-
Extralite::Database.new(@fn, wal: true).tap do
|
|
100
|
-
@count += 1
|
|
101
|
-
it.on_progress(mode: :at_least_once, period: 320, tick: 10) { @machine.snooze }
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|