diametric 0.0.4 → 0.1.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 +15 -0
- data/Gemfile +21 -18
- data/Jarfile +15 -1
- data/README.md +22 -14
- data/Rakefile +17 -1
- data/bin/datomic-rest +33 -0
- data/bin/download-datomic +13 -0
- data/datomic_version.yml +4 -0
- data/diametric.gemspec +9 -6
- data/ext/diametric/DiametricCollection.java +147 -0
- data/ext/diametric/DiametricConnection.java +113 -0
- data/ext/diametric/DiametricDatabase.java +107 -0
- data/ext/diametric/DiametricEntity.java +90 -0
- data/ext/diametric/DiametricListenableFuture.java +47 -0
- data/ext/diametric/DiametricObject.java +66 -0
- data/ext/diametric/DiametricPeer.java +414 -0
- data/ext/diametric/DiametricService.java +102 -0
- data/ext/diametric/DiametricUUID.java +61 -0
- data/ext/diametric/DiametricUtils.java +183 -0
- data/lib/boolean_type.rb +3 -0
- data/lib/diametric.rb +24 -0
- data/lib/diametric/entity.rb +219 -14
- data/lib/diametric/generators/active_model.rb +2 -2
- data/lib/diametric/persistence.rb +0 -1
- data/lib/diametric/persistence/common.rb +28 -9
- data/lib/diametric/persistence/peer.rb +122 -87
- data/lib/diametric/persistence/rest.rb +4 -3
- data/lib/diametric/query.rb +94 -23
- data/lib/diametric/rest_service.rb +74 -0
- data/lib/diametric/service_base.rb +77 -0
- data/lib/diametric/transactor.rb +86 -0
- data/lib/diametric/version.rb +1 -1
- data/lib/diametric_service.jar +0 -0
- data/lib/value_enums.rb +8 -0
- data/spec/conf_helper.rb +55 -0
- data/spec/config/free-transactor-template.properties +73 -0
- data/spec/config/logback.xml +59 -0
- data/spec/data/seattle-data0.dtm +452 -0
- data/spec/data/seattle-data1.dtm +326 -0
- data/spec/developer_create_sample.rb +39 -0
- data/spec/developer_query_spec.rb +120 -0
- data/spec/diametric/config_spec.rb +1 -1
- data/spec/diametric/entity_spec.rb +263 -0
- data/spec/diametric/peer_api_spec.rb +147 -0
- data/spec/diametric/persistence/peer_many2many_spec.rb +76 -0
- data/spec/diametric/persistence/peer_spec.rb +13 -22
- data/spec/diametric/persistence/rest_spec.rb +12 -19
- data/spec/diametric/query_spec.rb +4 -5
- data/spec/diametric/rest_service_spec.rb +56 -0
- data/spec/diametric/transactor_spec.rb +68 -0
- data/spec/integration_spec.rb +5 -3
- data/spec/parent_child_sample.rb +42 -0
- data/spec/peer_integration_spec.rb +106 -22
- data/spec/peer_seattle_spec.rb +200 -0
- data/spec/rc2013_seattle_big.rb +82 -0
- data/spec/rc2013_seattle_small.rb +60 -0
- data/spec/rc2013_simple_sample.rb +72 -0
- data/spec/seattle_integration_spec.rb +106 -0
- data/spec/simple_validation_sample.rb +31 -0
- data/spec/spec_helper.rb +31 -45
- data/spec/support/entities.rb +157 -0
- data/spec/support/gen_entity_class.rb +2 -0
- data/spec/support/persistence_examples.rb +9 -5
- data/spec/test_version_file.yml +4 -0
- metadata +131 -75
- data/Jarfile.lock +0 -134
- data/lib/jrclj.rb +0 -63
@@ -8,8 +8,8 @@ module Diametric
|
|
8
8
|
"#{klass}.all"
|
9
9
|
end
|
10
10
|
|
11
|
-
def self.find(klass, params=nil)
|
12
|
-
"#{klass}.get(#{params})"
|
11
|
+
def self.find(klass, params=nil, connection=nil, resolve=false)
|
12
|
+
"#{klass}.get(#{params}, #{connection}, #{resolve})"
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.build(klass, params=nil)
|
@@ -36,25 +36,44 @@ module Diametric
|
|
36
36
|
end
|
37
37
|
|
38
38
|
module ClassMethods
|
39
|
-
def create_schema
|
40
|
-
|
39
|
+
def create_schema(connection=nil)
|
40
|
+
if self.instance_variable_get("@peer")
|
41
|
+
connection ||= Diametric::Persistence::Peer.connect
|
42
|
+
connection.transact(schema)
|
43
|
+
else
|
44
|
+
transact(schema)
|
45
|
+
end
|
41
46
|
end
|
42
47
|
|
43
|
-
def all
|
44
|
-
|
48
|
+
def all(connection=nil, resolve=false)
|
49
|
+
if self.instance_variable_get("@peer")
|
50
|
+
connection ||= Diametric::Persistence::Peer.connect
|
51
|
+
end
|
52
|
+
Diametric::Query.new(self, connection, resolve).all
|
45
53
|
end
|
46
54
|
|
47
|
-
def first(conditions = {})
|
48
|
-
|
55
|
+
def first(conditions = {}, connection=nil, resolve=false)
|
56
|
+
if self.instance_variable_get("@peer")
|
57
|
+
connection ||= Diametric::Persistence::Peer.connect
|
58
|
+
end
|
59
|
+
where(conditions, connection, resolve).first
|
49
60
|
end
|
50
61
|
|
51
|
-
def where(conditions = {})
|
52
|
-
|
62
|
+
def where(conditions = {}, connection=nil, resolve=false)
|
63
|
+
if self.instance_variable_get("@peer")
|
64
|
+
connection ||= Diametric::Persistence::Peer.connect
|
65
|
+
end
|
66
|
+
query = Diametric::Query.new(self, connection, resolve)
|
53
67
|
query.where(conditions)
|
54
68
|
end
|
55
69
|
|
56
70
|
def filter(*filter)
|
57
|
-
|
71
|
+
if self.instance_variable_get("@peer")
|
72
|
+
connection = Diametric::Persistence::Peer.connect
|
73
|
+
else
|
74
|
+
connection = nil
|
75
|
+
end
|
76
|
+
query = Diametric::Query.new(self, connection, true)
|
58
77
|
query.filter(*filter)
|
59
78
|
end
|
60
79
|
end
|
@@ -1,118 +1,153 @@
|
|
1
|
-
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
|
2
|
-
raise "This module requires the use of JRuby."
|
3
|
-
end
|
4
|
-
|
5
|
-
require 'diametric'
|
6
1
|
require 'diametric/persistence/common'
|
7
2
|
|
8
|
-
require 'java'
|
9
|
-
require 'lock_jar'
|
10
|
-
lockfile = File.expand_path( "../../../../Jarfile.lock", __FILE__ )
|
11
|
-
# Loads the classpath with Jars from the lockfile
|
12
|
-
LockJar.load(lockfile)
|
13
|
-
|
14
|
-
require 'jrclj'
|
15
|
-
java_import "clojure.lang.Keyword"
|
16
|
-
|
17
3
|
module Diametric
|
18
4
|
module Persistence
|
19
5
|
module Peer
|
20
|
-
include_package "datomic"
|
21
6
|
|
22
|
-
|
23
|
-
|
7
|
+
def save(connection=nil)
|
8
|
+
return false unless valid?
|
9
|
+
return true unless changed?
|
10
|
+
connection ||= Diametric::Persistence::Peer.connect
|
24
11
|
|
25
|
-
|
26
|
-
|
27
|
-
base.send(:extend, ClassMethods)
|
28
|
-
@persisted_classes.add(base)
|
29
|
-
end
|
12
|
+
parsed_data = []
|
13
|
+
parse_tx_data(tx_data, parsed_data)
|
30
14
|
|
31
|
-
|
32
|
-
@connection
|
33
|
-
end
|
15
|
+
map = connection.transact(parsed_data).get
|
34
16
|
|
35
|
-
|
36
|
-
@persisted_classes.each do |klass|
|
37
|
-
klass.create_schema
|
38
|
-
end
|
39
|
-
end
|
17
|
+
resolve_changes([self], map)
|
40
18
|
|
41
|
-
|
42
|
-
|
19
|
+
map
|
20
|
+
end
|
43
21
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@connection = Java::Datomic::Peer.connect(uri)
|
22
|
+
def resolve_tempid(map, id)
|
23
|
+
if id.to_s =~ /-\d+/
|
24
|
+
return Diametric::Persistence::Peer.resolve_tempid(map, id)
|
48
25
|
end
|
26
|
+
return id
|
27
|
+
end
|
49
28
|
|
50
|
-
|
51
|
-
|
29
|
+
def parse_tx_data(data, result)
|
30
|
+
queue = []
|
31
|
+
data.each do |c_data|
|
32
|
+
if c_data.is_a? Hash
|
33
|
+
parse_hash_data(c_data, result, queue)
|
34
|
+
elsif c_data.is_a? Array
|
35
|
+
parse_array_data(c_data, result)
|
36
|
+
end
|
52
37
|
end
|
38
|
+
parse_tx_data(queue, result) unless queue.empty?
|
39
|
+
end
|
53
40
|
|
54
|
-
|
55
|
-
|
56
|
-
|
41
|
+
def parse_hash_data(c_hash, result, queue)
|
42
|
+
hash = {}
|
43
|
+
c_hash.each do |c_key, c_value|
|
44
|
+
if c_value.respond_to?(:tx_data)
|
45
|
+
if c_value.tx_data.empty?
|
46
|
+
hash[c_key] = c_value.dbid
|
47
|
+
else
|
48
|
+
c_value.dbid = c_value.tempid
|
49
|
+
hash[c_key] = c_value.dbid
|
50
|
+
queue << c_value.tx_data.first
|
51
|
+
end
|
52
|
+
elsif c_value.is_a? Set
|
53
|
+
set_value = Set.new
|
54
|
+
c_value.each do |s|
|
55
|
+
if s.respond_to?(:tx_data) && s.tx_data.empty?
|
56
|
+
set_value << s.dbid
|
57
|
+
elsif s.respond_to?(:tx_data)
|
58
|
+
set_value << s.tx_data[":db/id"]
|
59
|
+
parsed_tx_data(s, result)
|
60
|
+
else
|
61
|
+
set_value << s
|
62
|
+
end
|
63
|
+
end
|
64
|
+
hash[c_key] = set_value
|
65
|
+
elsif c_value.respond_to?(:eid)
|
66
|
+
hash[c_key] = c_value.eid
|
67
|
+
else
|
68
|
+
hash[c_key] = c_value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
result << hash
|
72
|
+
end
|
57
73
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
74
|
+
def parse_array_data(c_array, result)
|
75
|
+
array = []
|
76
|
+
c_array.each do |c_value|
|
77
|
+
if c_value.respond_to?(:to_a)
|
78
|
+
a_values = []
|
79
|
+
c_value.to_a.each do |a_value|
|
80
|
+
if a_value.respond_to?(:tx_data) && a_value.tx_data.empty?
|
81
|
+
a_values << a_value.dbid
|
82
|
+
else
|
83
|
+
a_values << a_value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
array << a_values
|
87
|
+
else
|
88
|
+
array << c_value
|
89
|
+
end
|
62
90
|
end
|
91
|
+
result << array
|
92
|
+
end
|
63
93
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
94
|
+
def resolve_changes(parents, map)
|
95
|
+
queue = []
|
96
|
+
parents.each do |child|
|
97
|
+
child.attributes.each do |a_key, a_val|
|
98
|
+
if a_val.respond_to?(:tx_data)
|
99
|
+
queue << a_val
|
100
|
+
end
|
69
101
|
end
|
70
|
-
|
71
|
-
|
102
|
+
child.instance_variable_set("@previously_changed", child.changes)
|
103
|
+
child.changed_attributes.clear
|
104
|
+
child.dbid = resolve_tempid(map, child.dbid)
|
105
|
+
child.instance_variable_set("@tx_map", map)
|
72
106
|
end
|
107
|
+
resolve_changes(queue, map) unless queue.empty?
|
108
|
+
end
|
73
109
|
|
74
|
-
|
75
|
-
|
76
|
-
|
110
|
+
def retract_entity(dbid)
|
111
|
+
Diametric::Persistence::Peer.retract_entity(dbid)
|
112
|
+
end
|
77
113
|
|
78
|
-
|
79
|
-
|
114
|
+
def method_missing(method_name, *args, &block)
|
115
|
+
result = /(.+)_from_this_(.+)/.match(method_name)
|
116
|
+
if result
|
117
|
+
query_string = ":#{result[1]}/_#{result[2]}"
|
118
|
+
entities = Diametric::Persistence::Peer.reverse_q(args[0].db, self.dbid, query_string)
|
119
|
+
entities.collect {|e| self.class.from_dbid_or_entity(e, args[0])}
|
120
|
+
else
|
121
|
+
super
|
80
122
|
end
|
81
123
|
end
|
82
124
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
# @return [ true, false ] True is success, false if not.
|
91
|
-
def save
|
92
|
-
return false unless valid?
|
93
|
-
return true unless changed?
|
94
|
-
|
95
|
-
res = self.class.transact(tx_data)
|
96
|
-
if dbid.nil?
|
97
|
-
self.dbid = Java::Datomic::Peer.resolve_tempid(
|
98
|
-
res[:"db-after".to_clj],
|
99
|
-
res[:tempids.to_clj],
|
100
|
-
self.class.clj.edn_convert(tempid))
|
125
|
+
module ClassMethods
|
126
|
+
def get(dbid, connection=nil, resolve=false)
|
127
|
+
entity = self.new
|
128
|
+
dbid = dbid.to_i if dbid.is_a?(String)
|
129
|
+
connection ||= Diametric::Persistence::Peer.connect
|
130
|
+
entity = self.from_dbid_or_entity(dbid, connection, resolve)
|
131
|
+
entity
|
101
132
|
end
|
102
133
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
self.dbid
|
111
|
-
end
|
134
|
+
def q(query, args, conn_or_db=nil)
|
135
|
+
conn_or_db ||= Diametric::Persistence::Peer.connect
|
136
|
+
if conn_or_db.is_a?(Diametric::Persistence::Connection)
|
137
|
+
db = conn_or_db.db
|
138
|
+
else
|
139
|
+
db = conn_or_db
|
140
|
+
end
|
112
141
|
|
113
|
-
|
114
|
-
|
115
|
-
|
142
|
+
results = Diametric::Persistence::Peer.q(query, db, args)
|
143
|
+
# Diametric query expects the first element of each array in
|
144
|
+
# results is dbid. Wraps dbid here by
|
145
|
+
# Diametric::Persistence::Object to make it consistent
|
146
|
+
results.each do |r|
|
147
|
+
r[0] = Diametric::Persistence::Object.new(r[0])
|
148
|
+
end
|
149
|
+
results
|
150
|
+
end
|
116
151
|
end
|
117
152
|
end
|
118
153
|
end
|
@@ -47,8 +47,9 @@ module Diametric
|
|
47
47
|
connection.transact(database, data)
|
48
48
|
end
|
49
49
|
|
50
|
-
def get(dbid)
|
51
|
-
|
50
|
+
def get(dbid, conn=nil, resolve=false)
|
51
|
+
conn ||= connection
|
52
|
+
res = conn.entity(database, dbid.to_i)
|
52
53
|
|
53
54
|
# TODO tighten regex to only allow fields with the model name
|
54
55
|
attrs = res.data.map { |attr_symbol, value|
|
@@ -61,7 +62,7 @@ module Diametric
|
|
61
62
|
entity
|
62
63
|
end
|
63
64
|
|
64
|
-
def q(query, args)
|
65
|
+
def q(query, args, unused=nil)
|
65
66
|
args.unshift(connection.db_alias(database))
|
66
67
|
res = connection.query(query, args)
|
67
68
|
res.data
|
data/lib/diametric/query.rb
CHANGED
@@ -15,16 +15,19 @@ module Diametric
|
|
15
15
|
class Query
|
16
16
|
include Enumerable
|
17
17
|
|
18
|
-
attr_reader :conditions, :filters, :model
|
18
|
+
attr_reader :conditions, :filters, :filter_attrs, :model, :connection, :resolve
|
19
19
|
|
20
20
|
# Create a new Datomic query.
|
21
21
|
#
|
22
22
|
# @param model [Entity] This model must include +Datomic::Entity+. Including
|
23
23
|
# a persistence module is optional.
|
24
|
-
def initialize(model)
|
24
|
+
def initialize(model, connection_or_database=nil, resolve=false)
|
25
25
|
@model = model
|
26
26
|
@conditions = {}
|
27
27
|
@filters = []
|
28
|
+
@filter_attrs = []
|
29
|
+
@conn_or_db = connection_or_database
|
30
|
+
@resolve = resolve
|
28
31
|
end
|
29
32
|
|
30
33
|
# Add conditions to your Datomic query. Conditions check for equality
|
@@ -69,6 +72,7 @@ module Diametric
|
|
69
72
|
# that will be converted into a Datalog query.
|
70
73
|
# @return [Query]
|
71
74
|
def filter(*filter)
|
75
|
+
return peer_filter(filter) if self.model.instance_variable_get("@peer")
|
72
76
|
query = self.dup
|
73
77
|
|
74
78
|
if filter.first.is_a?(EDN::Type::List)
|
@@ -82,6 +86,22 @@ module Diametric
|
|
82
86
|
query
|
83
87
|
end
|
84
88
|
|
89
|
+
def peer_filter(*filter)
|
90
|
+
query = self.dup
|
91
|
+
|
92
|
+
if filter.first.is_a?(Array)
|
93
|
+
filter = filter.first
|
94
|
+
end
|
95
|
+
filter_attrs << filter[1].to_s
|
96
|
+
filter[1] = "?#{filter[1].to_s}"
|
97
|
+
filter = filter.tap {|e| e.to_s }.join(" ")
|
98
|
+
filter = "[(#{filter})]"
|
99
|
+
|
100
|
+
query.filters << filter
|
101
|
+
query
|
102
|
+
end
|
103
|
+
|
104
|
+
|
85
105
|
# Loop through the query results. In order to use +each+, your model *must*
|
86
106
|
# include a persistence API. At a minimum, it must have a +.q+ method that
|
87
107
|
# returns an +Enumerable+ object.
|
@@ -90,12 +110,17 @@ module Diametric
|
|
90
110
|
def each
|
91
111
|
# TODO check to see if the model has a `.q` method and give
|
92
112
|
# an appropriate error if not.
|
93
|
-
res = model.q(*data)
|
94
|
-
|
113
|
+
res = model.q(*data, @conn_or_db)
|
95
114
|
collapse_results(res).each do |entity|
|
115
|
+
if self.model.instance_variable_get("@peer") && @resolve
|
116
|
+
yield model.from_dbid_or_entity(entity.first.to_java, @conn_or_db, @resolve)
|
117
|
+
elsif self.model.instance_variable_get("@peer")
|
118
|
+
yield entity.first.to_java
|
96
119
|
# The map is for compatibility with Java peer persistence.
|
97
120
|
# TODO remove if possible
|
98
|
-
|
121
|
+
else
|
122
|
+
yield model.from_query(entity.map { |x| x }, @conn_or_db, @resolve)
|
123
|
+
end
|
99
124
|
end
|
100
125
|
end
|
101
126
|
|
@@ -113,6 +138,8 @@ module Diametric
|
|
113
138
|
# is the Datomic query composed of Ruby data. The second element is
|
114
139
|
# the arguments that used with the query.
|
115
140
|
def data
|
141
|
+
return peer_data if self.model.instance_variable_get("@peer")
|
142
|
+
|
116
143
|
vars = model.attribute_names.map { |attribute| ~"?#{attribute}" }
|
117
144
|
|
118
145
|
from = conditions.map { |k, _| ~"?#{k}" }
|
@@ -133,6 +160,39 @@ module Diametric
|
|
133
160
|
[query, args]
|
134
161
|
end
|
135
162
|
|
163
|
+
def peer_data
|
164
|
+
if conditions.empty? && filters.empty?
|
165
|
+
args = [model.prefix]
|
166
|
+
query = <<-EOQ
|
167
|
+
[:find ?e
|
168
|
+
:in $ [?include-ns ...]
|
169
|
+
:where
|
170
|
+
[?e ?aid ?v]
|
171
|
+
[?aid :db/ident ?a]
|
172
|
+
[(namespace ?a) ?ns]
|
173
|
+
[(= ?ns ?include-ns)]]
|
174
|
+
EOQ
|
175
|
+
else
|
176
|
+
unless conditions.empty?
|
177
|
+
clauses = conditions.keys.inject("") do |memo, attribute|
|
178
|
+
memo + "[?e " + model.namespace(model.prefix, attribute) + " ?#{attribute} ] "
|
179
|
+
end
|
180
|
+
from = conditions.inject("[") {|memo, kv| memo + "?#{kv.shift} "} +"]"
|
181
|
+
args = conditions.map { |_, v| v }
|
182
|
+
end
|
183
|
+
|
184
|
+
unless filter_attrs.empty?
|
185
|
+
clauses ||= ""
|
186
|
+
clauses = filter_attrs.inject(clauses) do |memo, attribute|
|
187
|
+
memo + "[?e " + model.namespace(model.prefix, attribute) + " ?#{attribute} ] "
|
188
|
+
end
|
189
|
+
end
|
190
|
+
query = "[:find ?e :in $ #{from} :where #{clauses} #{filters.join}]"
|
191
|
+
end
|
192
|
+
|
193
|
+
[query, args]
|
194
|
+
end
|
195
|
+
|
136
196
|
protected
|
137
197
|
|
138
198
|
def conditions=(conditions)
|
@@ -156,32 +216,43 @@ module Diametric
|
|
156
216
|
end
|
157
217
|
|
158
218
|
def collapse_results(res)
|
219
|
+
return res unless @resolve
|
220
|
+
|
159
221
|
res.group_by(&:first).map do |dbid, results|
|
160
222
|
# extract dbid from results
|
161
223
|
# NOTE: we prefer to_a.drop(1) over block arg decomposition because
|
162
224
|
# result may be a Java::ClojureLang::PersistentVector
|
163
225
|
results = results.map {|result| result.to_a.drop(1) }
|
164
226
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
227
|
+
unless results.flatten.empty?
|
228
|
+
# Group values from all results into one result set
|
229
|
+
# [["b", 123], ["c", 123]] #=> [["b", "c"], [123, 123]]
|
230
|
+
grouped_values = results.transpose
|
231
|
+
attr_grouped_values = grouped_values[0...model.attributes.size]
|
232
|
+
enum_grouped_values = grouped_values[model.attributes.size..-1]
|
233
|
+
|
234
|
+
# Attach attribute names to each collection of values
|
235
|
+
# => [[:letters, ["b", "c"]], [:number, [123, 123]]]
|
236
|
+
attr_to_values = model.attributes.keys.zip(attr_grouped_values)
|
237
|
+
|
238
|
+
# Retain cardinality/many attributes as a collection,
|
239
|
+
# but pick only one value for cardinality/one attributes
|
240
|
+
collapsed_values = attr_to_values.map do |attr, values|
|
241
|
+
if model.attributes[attr][:cardinality] == :many
|
242
|
+
values
|
243
|
+
elsif model.attributes[attr][:value_type] == "ref" &&
|
244
|
+
model.enum_names.include?(attr)
|
245
|
+
enum_grouped_values.shift.first
|
246
|
+
else
|
247
|
+
values.first
|
248
|
+
end
|
180
249
|
end
|
181
|
-
end
|
182
250
|
|
183
|
-
|
184
|
-
|
251
|
+
# Returning a singular result for each dbid
|
252
|
+
[dbid, *collapsed_values]
|
253
|
+
else
|
254
|
+
[dbid]
|
255
|
+
end
|
185
256
|
end
|
186
257
|
end
|
187
258
|
end
|