thingfish-metastore-pggraph 0.1.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: aa350287482cc8fd51a482da2133f97e31b01d9d
4
+ data.tar.gz: ad7d4eeeb9db0849b5fa90b9f76633075fb81134
5
+ SHA512:
6
+ metadata.gz: b2b3bad22b9291d94bd5c9ce659fd2ce3cedb9956aad6b35d1dee21421d97bbca90ceb96450d91ff58e2f8f1bc1a1c1e15ad2f99444606807b4833e0fb037746
7
+ data.tar.gz: 30b2e55811bd4ade8794f3407a47476e57175245a9914fc45b0cf529dfaecb139ca2017c224612dc7b8c9bd71381aa4de7537cdb71b8ac3fe12ab7d6d32d275c
@@ -0,0 +1,45 @@
1
+ # vim: set nosta noet ts=4 sw=4:
2
+
3
+ ### The initial Thingfish::Metastore::PgGraph DDL.
4
+ ###
5
+ Sequel.migration do
6
+ up do
7
+ create_table( :nodes ) do
8
+ uuid :id, primary_key: true
9
+ text :format, null: false
10
+ int :extent, null: false
11
+ timestamptz :created, null: false, default: Sequel.function(:now)
12
+ inet :uploadaddress, null: false
13
+ jsonb :user_metadata, null: false, default: '{}'
14
+ end
15
+
16
+ create_table( :edges ) do
17
+ uuid :id_p, null: false
18
+ uuid :id_c, null: false
19
+ jsonb :prop, null: false, default: '{}'
20
+
21
+ index :id_p
22
+ index :id_c
23
+
24
+ # Remove relationships when a node is deleted.
25
+ foreign_key [:id_p], :nodes, name: 'edges_p_fkey', key: :id, on_delete: :cascade, on_update: :cascade
26
+ foreign_key [:id_c], :nodes, name: 'edges_c_fkey', key: :id, on_delete: :cascade, on_update: :cascade
27
+ end
28
+
29
+ # Add functional index from JSON edge props
30
+ run "CREATE INDEX relation_idx ON edges ( (prop->>'relationship') )"
31
+
32
+ # Disallow a node linking to itself -- no self loops.
33
+ run 'ALTER TABLE edges ADD CONSTRAINT no_self_edge_chk CHECK ( id_p <> id_c )'
34
+
35
+ # Allow only a single link between any two nodes, enforcing relations
36
+ # to be directional parent->child.
37
+ run 'CREATE UNIQUE INDEX pair_unique_idx ON edges USING btree ((LEAST(id_p, id_c)), (GREATEST(id_p, id_c)))'
38
+ end
39
+
40
+ down do
41
+ drop_table( :nodes, cascade: true )
42
+ drop_table( :edges, cascade: true )
43
+ end
44
+ end
45
+
@@ -0,0 +1,311 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'loggability'
5
+ require 'configurability'
6
+ require 'sequel'
7
+ require 'strelka'
8
+ require 'strelka/mixins'
9
+
10
+ require 'thingfish'
11
+ require 'thingfish/mixins'
12
+ require 'thingfish/metastore'
13
+
14
+ # Toplevel namespace
15
+ class Thingfish::Metastore::PgGraph < Thingfish::Metastore
16
+ extend Loggability,
17
+ Configurability,
18
+ Strelka::MethodUtilities
19
+ include Thingfish::Normalization
20
+
21
+
22
+ # Load Sequel extensions/plugins
23
+ Sequel.extension :migration
24
+
25
+
26
+ # Package version
27
+ VERSION = '0.1.3'
28
+
29
+ # Version control revision
30
+ REVISION = %q$Revision: 1ad0d5bc5083 $
31
+
32
+ # The data directory that contains migration files.
33
+ #
34
+ DATADIR = if ENV['THINGFISH_METASTORE_PGGRAPH_DATADIR']
35
+ Pathname.new( ENV['THINGFISH_METASTORE_PGGRAPH_DATADIR'] )
36
+ elsif Gem.datadir( 'thingfish-metastore-pggraph' )
37
+ Pathname.new( Gem.datadir('thingfish-metastore-pggraph') )
38
+ else
39
+ Pathname.new( __FILE__ ).dirname.parent.parent.parent +
40
+ 'data' + 'thingfish-metastore-pggraph'
41
+ end
42
+
43
+ # The default config values
44
+ DEFAULT_CONFIG = {
45
+ uri: 'postgres:/thingfish',
46
+ slow_query_seconds: 0.01,
47
+ }
48
+
49
+
50
+ # Loggability API -- use a separate logger
51
+ log_as :thingfish_metastore_pggraph
52
+
53
+ # Configurability API -- load the `pg_metastore`
54
+ config_key :pggraph_metastore
55
+
56
+ ##
57
+ # The URI of the database to use for the metastore
58
+ singleton_attr_accessor :uri
59
+
60
+ ##
61
+ # The Sequel::Database that's used to access the metastore tables
62
+ singleton_attr_accessor :db
63
+
64
+ ##
65
+ # The number of seconds to consider a "slow" query
66
+ singleton_attr_accessor :slow_query_seconds
67
+
68
+
69
+ ### Set up the metastore database and migrate to the latest version.
70
+ def self::setup_database
71
+ Sequel.extension :pg_json_ops
72
+
73
+ self.db = Sequel.connect( self.uri )
74
+ self.db.logger = Loggability[ Thingfish::Metastore::PgGraph ]
75
+ self.db.extension :pg_streaming
76
+ self.db.stream_all_queries = true
77
+ self.db.optimize_model_load = true
78
+ self.db.sql_log_level = :debug
79
+ self.db.extension( :pg_json )
80
+ self.db.log_warn_duration = self.slow_query_seconds
81
+
82
+ # Ensure the database is current.
83
+ #
84
+ unless Sequel::Migrator.is_current?( self.db, self.migrations_dir.to_s )
85
+ self.log.info "Installing database schema..."
86
+ Sequel::Migrator.apply( self.db, self.migrations_dir.to_s )
87
+ end
88
+ end
89
+
90
+
91
+ ### Tear down the configured metastore database.
92
+ def self::teardown_database
93
+ self.log.info "Tearing down database schema..."
94
+ Sequel::Migrator.apply( self.db, self.migrations_dir.to_s, 0 )
95
+ end
96
+
97
+
98
+ ### Return the current database migrations directory as a Pathname
99
+ def self::migrations_dir
100
+ return DATADIR + 'migrations'
101
+ end
102
+
103
+
104
+ ### Configurability API -- set up the metastore with the `pg_metastore` section of
105
+ ### the config file.
106
+ def self::configure( config=nil )
107
+ config = self.defaults.merge( config || {} )
108
+
109
+ self.uri = config[:uri]
110
+ self.slow_query_seconds = config[:slow_query_seconds]
111
+
112
+ self.setup_database
113
+ end
114
+
115
+
116
+ ### Set up the metastore.
117
+ def initialize( * ) # :notnew:
118
+ require 'thingfish/metastore/pggraph/node'
119
+ require 'thingfish/metastore/pggraph/edge'
120
+ Thingfish::Metastore::PgGraph::Node.db = self.class.db
121
+ Thingfish::Metastore::PgGraph::Edge.db = self.class.db
122
+ @model = Thingfish::Metastore::PgGraph::Node
123
+ end
124
+
125
+
126
+ ######
127
+ public
128
+ ######
129
+
130
+ ##
131
+ # The Sequel model representing the metadata rows.
132
+ attr_reader :model
133
+
134
+
135
+ #
136
+ # :section: Thingfish::Metastore API
137
+ #
138
+
139
+ ### Return an Array of all stored oids.
140
+ def oids
141
+ return self.each_oid.to_a
142
+ end
143
+
144
+
145
+ ### Iterate over each of the store's oids, yielding to the block if one is given
146
+ ### or returning an Enumerator if one is not.
147
+ def each_oid( &block )
148
+ return self.model.select_map( :id ).each( &block )
149
+ end
150
+
151
+
152
+ ### Save the +metadata+ Hash for the specified +oid+.
153
+ def save( oid, metadata )
154
+ md = self.model.from_hash( metadata )
155
+ md.id = oid
156
+ md.save
157
+ end
158
+
159
+
160
+ ### Fetch the data corresponding to the given +oid+ as a Hash-ish object.
161
+ def fetch( oid, *keys )
162
+ metadata = self.model[ oid ] or return nil
163
+
164
+ if keys.empty?
165
+ return metadata.to_hash
166
+ else
167
+ keys = normalize_keys( keys )
168
+ values = metadata.to_hash.values_at( *keys )
169
+ return Hash[ [keys, values].transpose ]
170
+ end
171
+ end
172
+
173
+
174
+ ### Fetch the value of the metadata associated with the given +key+ for the
175
+ ### specified +oid+.
176
+ def fetch_value( oid, key )
177
+ metadata = self.model[ oid ] or return nil
178
+ key = key.to_sym
179
+ return metadata[ key ] || metadata.user_metadata[ key ]
180
+ end
181
+
182
+
183
+ ### Fetch UUIDs related to the given +oid+.
184
+ def fetch_related_oids( oid )
185
+ oid = normalize_oid( oid )
186
+ oid = self.model[ oid ]
187
+ return [] unless oid
188
+ return oid.related_nodes.map( &:id_c )
189
+ end
190
+
191
+
192
+ ### Search the metastore for UUIDs which match the specified +criteria+ and
193
+ ### return them as an iterator.
194
+ def search( options={} )
195
+ ds = self.model.naked.select( :id )
196
+ self.log.debug "Starting search with %p" % [ ds ]
197
+
198
+ ds = self.omit_related_resources( ds, options )
199
+ ds = self.apply_search_criteria( ds, options )
200
+ ds = self.apply_search_order( ds, options )
201
+ ds = self.apply_search_direction( ds, options )
202
+ ds = self.apply_search_limit( ds, options )
203
+
204
+ self.log.debug "Dataset for search is: %s" % [ ds.sql ]
205
+
206
+ return ds.map {|row| row[:id] }
207
+ end
208
+
209
+
210
+ ### Update the metadata for the given +oid+ with the specified +values+ hash.
211
+ def merge( oid, values )
212
+ values = normalize_keys( values )
213
+
214
+ md = self.model[ oid ] or return nil
215
+ md.merge!( values )
216
+ md.save
217
+ end
218
+
219
+
220
+ ### Remove all metadata associated with +oid+ from the Metastore.
221
+ def remove( oid, *keys )
222
+ self.model[ id: oid ].destroy
223
+ end
224
+
225
+
226
+ ### Remove all metadata associated with +oid+ except for the specified +keys+.
227
+ def remove_except( oid, *keys )
228
+ keys = normalize_keys( keys )
229
+
230
+ md = self.model[ oid ] or return nil
231
+ md.user_metadata.keep_if {|key,_| keys.include?(key) }
232
+ md.save
233
+ end
234
+
235
+
236
+ ### Returns +true+ if the metastore has metadata associated with the specified +oid+.
237
+ def include?( oid )
238
+ return self.model.count( id: oid ).nonzero?
239
+ end
240
+
241
+
242
+ ### Returns the number of objects the store contains.
243
+ def size
244
+ return self.model.count
245
+ end
246
+
247
+
248
+ #########
249
+ protected
250
+ #########
251
+
252
+ ### Omit related resources from the search dataset +ds+ unless the given
253
+ ### +options+ specify otherwise.
254
+ def omit_related_resources( ds, options )
255
+ unless options[:include_related]
256
+ self.log.debug " omitting entries for related resources"
257
+ ds = ds.unrelated
258
+ end
259
+ return ds
260
+ end
261
+
262
+
263
+ ### Apply the search :criteria from the specified +options+ to the collection
264
+ ### in +ds+ and return the modified dataset.
265
+ def apply_search_criteria( ds, options )
266
+ if (( criteria = options[:criteria] ))
267
+ criteria.each do |field, value|
268
+ self.log.debug " applying criteria: %p => %p" % [ field.to_s, value ]
269
+ ds = ds.where_metadata( field => value )
270
+ end
271
+ end
272
+
273
+ return ds
274
+ end
275
+
276
+
277
+ ### Apply the search :order from the specified +options+ to the collection in
278
+ ### +ds+ and return the modified dataset.
279
+ def apply_search_order( ds, options )
280
+ if options[:order]
281
+ columns = Array( options[:order] )
282
+ self.log.debug " ordering results by columns: %p" % [ columns ]
283
+ ds = ds.order_metadata( columns )
284
+ end
285
+
286
+ return ds
287
+ end
288
+
289
+
290
+ ### Apply the search :direction from the specified +options+ to the collection
291
+ ### in +ds+ and return the modified dataset.
292
+ def apply_search_direction( ds, options )
293
+ ds = ds.reverse if options[:direction] && options[:direction] == 'desc'
294
+ return ds
295
+ end
296
+
297
+
298
+ ### Apply the search :limit from the specified +options+ to the collection in
299
+ ### +ds+ and return the modified dataset.
300
+ def apply_search_limit( ds, options )
301
+ if (( limit = options[:limit] ))
302
+ self.log.debug " limiting to %s results" % [ limit ]
303
+ offset = options[:offset] || 0
304
+ ds = ds.limit( limit, offset )
305
+ end
306
+
307
+ return ds
308
+ end
309
+
310
+ end # class Thingfish::Metastore::PgGraph
311
+
@@ -0,0 +1,52 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'sequel/model'
5
+
6
+ require 'thingfish/mixins'
7
+ require 'thingfish/metastore/pggraph' unless defined?( Thingfish::Metastore::PgGraph )
8
+
9
+
10
+ ### A row representing a relationship between two node objects.
11
+ ###
12
+ class Thingfish::Metastore::PgGraph::Edge < Sequel::Model( :edges )
13
+
14
+ # Related resource associations
15
+ many_to_one :node, :key => :id_p
16
+
17
+ # Dataset methods
18
+ #
19
+ dataset_module do
20
+ #########
21
+ protected
22
+ #########
23
+
24
+ ### Returns a Sequel expression suitable for use as the key of a query against
25
+ ### the specified property field.
26
+ ###
27
+ def prop_expr( field )
28
+ return Sequel.pg_jsonb( :prop ).get_text( field.to_s )
29
+ end
30
+ end
31
+
32
+
33
+ ### Do some initial attribute setup for new objects.
34
+ ###
35
+ def initialize( * )
36
+ super
37
+ self[ :prop ] ||= Sequel.pg_jsonb({})
38
+ end
39
+
40
+
41
+ #########
42
+ protected
43
+ #########
44
+
45
+ ### Proxy method -- fetch a value from the edge property hash if it exists.
46
+ ###
47
+ def method_missing( sym, *args, &block )
48
+ return self.prop[ sym.to_s ] || super
49
+ end
50
+
51
+ end # Thingfish::Metastore::PgGraph::Edge
52
+
@@ -0,0 +1,202 @@
1
+ # -*- ruby -*-
2
+ #encoding: utf-8
3
+
4
+ require 'sequel/model'
5
+
6
+ require 'thingfish/mixins'
7
+ require 'thingfish/metastore/pggraph' unless defined?( Thingfish::Metastore::PgGraph )
8
+
9
+
10
+ # A row of metadata describing an asset in a Thingfish store.
11
+ class Thingfish::Metastore::PgGraph::Node < Sequel::Model( :nodes )
12
+ include Thingfish::Normalization
13
+
14
+ # Related resources for this node
15
+ one_to_many :related_nodes, :key => :id_p, :class => 'Thingfish::Metastore::PgGraph::Edge'
16
+
17
+ # Edge relation if this node is a related resource
18
+ one_to_one :related_to, :key => :id_c, :class => 'Thingfish::Metastore::PgGraph::Edge'
19
+
20
+ # Allow instances to be created with a primary key
21
+ unrestrict_primary_key
22
+
23
+
24
+ # Dataset methods
25
+ dataset_module do
26
+
27
+ ### Dataset method: Limit results to metadata which is for a related resource.
28
+ ###
29
+ def related
30
+ return self.join_edges( :rel ).exclude( :rel__id_c => nil )
31
+ end
32
+
33
+
34
+ ### Dataset method: Limit results to metadata which is not for a related resource.
35
+ ###
36
+ def unrelated
37
+ return self.join_edges( :notrel ).filter( :notrel__id_c => nil )
38
+ end
39
+
40
+
41
+ ### Dataset method: Limit results to records whose operational or user
42
+ ### metadata matches the values from the specified +hash+.
43
+ ###
44
+ def where_metadata( hash )
45
+ ds = self
46
+ hash.each do |field, value|
47
+
48
+ # Direct DB column
49
+ #
50
+ if self.model.metadata_columns.include?( field.to_sym )
51
+ ds = ds.where( field.to_sym => value )
52
+
53
+ # User metadata or edge relationship
54
+ #
55
+ else
56
+ if field.to_sym == :relationship
57
+ ds = ds.join_edges unless ds.joined_dataset?
58
+ ds = ds.filter( Sequel.pg_jsonb( :edges__prop ).get_text( field.to_s ) => value )
59
+
60
+ elsif field.to_sym == :relation
61
+ ds = ds.join_edges unless ds.joined_dataset?
62
+ ds = self.join_edges.filter( :edges__id_p => value )
63
+
64
+ else
65
+ ds = ds.where( self.user_metadata_expr(field) => value )
66
+ end
67
+ end
68
+ end
69
+
70
+ return ds
71
+ end
72
+
73
+
74
+ ### Dataset method: Order results by the specified +columns+.
75
+ ###
76
+ def order_metadata( *columns )
77
+ columns.flatten!
78
+ ds = self
79
+ columns.each do |column|
80
+ if Thingfish::Metastore::PgGraph::Node.metadata_columns.include?( column.to_sym )
81
+ ds = ds.order_append( column.to_sym )
82
+ else
83
+ ds = ds.order_append( self.user_metadata_expr(column) )
84
+ end
85
+ end
86
+
87
+ return ds
88
+ end
89
+
90
+
91
+
92
+ #########
93
+ protected
94
+ #########
95
+
96
+ ### Return a dataset linking related nodes to edges.
97
+ ###
98
+ def join_edges( aka=nil )
99
+ return self.join_table( :left, :edges, { :id_c => :nodes__id }, { :table_alias => aka } )
100
+ end
101
+
102
+
103
+ ### Returns a Sequel expression suitable for use as the key of a query against
104
+ ### the specified user metadata field.
105
+ def user_metadata_expr( field )
106
+ return Sequel.pg_jsonb( :user_metadata ).get_text( field.to_s )
107
+ end
108
+
109
+ end # dataset_module
110
+
111
+
112
+ ### Return a new Metadata object from the given +oid+ and one-dimensional +hash+
113
+ ### used by Thingfish.
114
+ def self::from_hash( hash )
115
+ metadata = Thingfish::Normalization.normalize_keys( hash )
116
+
117
+ md = new
118
+
119
+ md.format = metadata.delete( 'format' )
120
+ md.extent = metadata.delete( 'extent' )
121
+ md.created = metadata.delete( 'created' )
122
+ md.uploadaddress = metadata.delete( 'uploadaddress' ).to_s
123
+
124
+ md.user_metadata = Sequel.pg_jsonb( metadata )
125
+
126
+ return md
127
+ end
128
+
129
+
130
+ ### Return the columns of the table that are used for resource metadata.
131
+ def self::metadata_columns
132
+ return self.columns - [self.primary_key, :user_metadata]
133
+ end
134
+
135
+
136
+ ### Do some initial attribute setup for new objects.
137
+ def initialize( * )
138
+ super
139
+ self[ :user_metadata ] ||= Sequel.pg_jsonb({})
140
+ end
141
+
142
+
143
+ ### Return the metadata as a Hash; overridden from Sequel::Model to
144
+ ### merge the user and system pairs together.
145
+ def to_hash
146
+ hash = self.values.dup
147
+
148
+ hash.delete( :id )
149
+ hash.merge!( hash.delete(:user_metadata) )
150
+
151
+ if related_to = self.related_to
152
+ hash.merge!( related_to.prop )
153
+ hash[ :relation ] = related_to.id_p
154
+ end
155
+
156
+ return normalize_keys( hash )
157
+ end
158
+
159
+
160
+ ### Merge new metadata +values+ into the metadata for the resource
161
+ def merge!( values )
162
+
163
+ # Extract and set the column-metadata values first
164
+ self.class.metadata_columns.each do |col|
165
+ next unless values.key?( col.to_s )
166
+ self[ col ] = values.delete( col.to_s )
167
+ end
168
+
169
+ self.user_metadata.merge!( values )
170
+ end
171
+
172
+
173
+ ### Hook creation for new related resources, divert relation data to
174
+ ### a new edge row.
175
+ ###
176
+ def around_save
177
+ relationship = self.user_metadata.delete( 'relationship' )
178
+ relation = self.user_metadata.delete( 'relation' )
179
+
180
+ super
181
+
182
+ if relation
183
+ edge = Thingfish::Metastore::PgGraph::Edge.new
184
+ edge.prop[ 'relationship' ] = relationship
185
+ edge.id_p = relation
186
+ edge.id_c = self.id
187
+ edge.save
188
+ end
189
+ end
190
+
191
+
192
+ #########
193
+ protected
194
+ #########
195
+
196
+ ### Proxy method -- fetch a value from the metadata hash if it exists.
197
+ def method_missing( sym, *args, &block )
198
+ return self.user_metadata[ sym.to_s ] || super
199
+ end
200
+
201
+ end # Thingfish::Metastore::PgGraph::Node
202
+
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thingfish-metastore-pggraph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Mahlon E. Smith <mahlon@martini.nu>
8
+ - Michael Granger <ged@faeriemud.org>
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-11-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thingfish
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.5'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '0.5'
28
+ - !ruby/object:Gem::Dependency
29
+ name: loggability
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0.11'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.11'
42
+ - !ruby/object:Gem::Dependency
43
+ name: configurability
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.2'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '2.2'
56
+ - !ruby/object:Gem::Dependency
57
+ name: sequel
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '4.35'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '4.35'
70
+ - !ruby/object:Gem::Dependency
71
+ name: pg
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0.19'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0.19'
84
+ description: |
85
+ This is a metadata storage plugin for the Thingfish digital asset
86
+ manager. It provides persistent storage for uploaded data to PostgreSQL
87
+ tables.
88
+
89
+ It is heavily based on the regular PG metastore, however it differs by
90
+ storing objects as nodes, and their relations as edges.
91
+ email: mahlon@martini.nu
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - data/thingfish-metastore-pggraph/migrations/20151102_initial.rb
97
+ - lib/thingfish/metastore/pggraph.rb
98
+ - lib/thingfish/metastore/pggraph/edge.rb
99
+ - lib/thingfish/metastore/pggraph/node.rb
100
+ homepage: https://bitbucket.org/mahlon/thingfish-metastore-pggraph
101
+ licenses:
102
+ - BSD-3-Clause
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '2.3'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.6.8
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Graph DDL storage for Thingfish metadata.
124
+ test_files: []