rdbr 0.1.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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +36 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +155 -0
  5. data/app/public/application.js +609 -0
  6. data/app/public/database-diagram.js +602 -0
  7. data/app/public/styles.css +2141 -0
  8. data/app/views/data.haml +166 -0
  9. data/app/views/group.haml +50 -0
  10. data/app/views/index.haml +108 -0
  11. data/app/views/layout.haml +59 -0
  12. data/app/views/record.haml +81 -0
  13. data/demo/README.md +47 -0
  14. data/demo/compose.yml +34 -0
  15. data/demo/seed.rb +270 -0
  16. data/demo/smoke.rb +13 -0
  17. data/docs/RELEASING.md +30 -0
  18. data/docs/VALUE_PRESENTATION.md +54 -0
  19. data/exe/rdbr +5 -0
  20. data/lib/rdbr/access/read_only.rb +61 -0
  21. data/lib/rdbr/access.rb +6 -0
  22. data/lib/rdbr/browser_launcher.rb +71 -0
  23. data/lib/rdbr/catalog/connection.rb +68 -0
  24. data/lib/rdbr/catalog/errors.rb +12 -0
  25. data/lib/rdbr/catalog/mysql/queries.rb +131 -0
  26. data/lib/rdbr/catalog/mysql.rb +146 -0
  27. data/lib/rdbr/catalog/postgresql/queries.rb +171 -0
  28. data/lib/rdbr/catalog/postgresql.rb +138 -0
  29. data/lib/rdbr/catalog/reader.rb +30 -0
  30. data/lib/rdbr/catalog/serializer.rb +24 -0
  31. data/lib/rdbr/catalog/sqlite.rb +182 -0
  32. data/lib/rdbr/catalog.rb +20 -0
  33. data/lib/rdbr/cli.rb +74 -0
  34. data/lib/rdbr/cli_options.rb +63 -0
  35. data/lib/rdbr/metadata/column.rb +44 -0
  36. data/lib/rdbr/metadata/constraints.rb +25 -0
  37. data/lib/rdbr/metadata/database.rb +37 -0
  38. data/lib/rdbr/metadata/index.rb +30 -0
  39. data/lib/rdbr/metadata/keys.rb +56 -0
  40. data/lib/rdbr/metadata/namespace.rb +26 -0
  41. data/lib/rdbr/metadata/relation.rb +104 -0
  42. data/lib/rdbr/metadata/relationship_inference.rb +75 -0
  43. data/lib/rdbr/metadata/relationships.rb +126 -0
  44. data/lib/rdbr/metadata/values.rb +73 -0
  45. data/lib/rdbr/metadata.rb +10 -0
  46. data/lib/rdbr/presentation/record_identity.rb +125 -0
  47. data/lib/rdbr/query/connection.rb +138 -0
  48. data/lib/rdbr/query/errors.rb +33 -0
  49. data/lib/rdbr/query/lookup.rb +40 -0
  50. data/lib/rdbr/query/mysql.rb +15 -0
  51. data/lib/rdbr/query/page.rb +66 -0
  52. data/lib/rdbr/query/postgresql.rb +7 -0
  53. data/lib/rdbr/query/postgresql_dialect.rb +11 -0
  54. data/lib/rdbr/query/relational.rb +239 -0
  55. data/lib/rdbr/query/sqlite.rb +15 -0
  56. data/lib/rdbr/query/type_support.rb +68 -0
  57. data/lib/rdbr/query/value_limiter.rb +38 -0
  58. data/lib/rdbr/query.rb +32 -0
  59. data/lib/rdbr/version.rb +3 -0
  60. data/lib/rdbr/web/application.rb +234 -0
  61. data/lib/rdbr/web/array_value_helpers.rb +92 -0
  62. data/lib/rdbr/web/binary_value_helpers.rb +51 -0
  63. data/lib/rdbr/web/column_preferences.rb +56 -0
  64. data/lib/rdbr/web/database_graph.rb +91 -0
  65. data/lib/rdbr/web/deferred_column_value_helpers.rb +20 -0
  66. data/lib/rdbr/web/deferred_value_helpers.rb +76 -0
  67. data/lib/rdbr/web/document_value_helpers.rb +68 -0
  68. data/lib/rdbr/web/filter_presentation_helpers.rb +44 -0
  69. data/lib/rdbr/web/helpers.rb +42 -0
  70. data/lib/rdbr/web/identifier_value_helpers.rb +20 -0
  71. data/lib/rdbr/web/json_page_stream.rb +30 -0
  72. data/lib/rdbr/web/navigation_helpers.rb +115 -0
  73. data/lib/rdbr/web/presentation_helpers.rb +114 -0
  74. data/lib/rdbr/web/range_value_helpers.rb +86 -0
  75. data/lib/rdbr/web/record_value_helpers.rb +90 -0
  76. data/lib/rdbr/web/related_value_helpers.rb +99 -0
  77. data/lib/rdbr/web/relationship_loader.rb +65 -0
  78. data/lib/rdbr/web/search_value_helpers.rb +58 -0
  79. data/lib/rdbr/web/spatial_value_helpers.rb +83 -0
  80. data/lib/rdbr/web/through_relationship_loader.rb +90 -0
  81. data/lib/rdbr/web/vector_value_helpers.rb +59 -0
  82. data/lib/rdbr.rb +25 -0
  83. metadata +298 -0
@@ -0,0 +1,65 @@
1
+ module RDBr
2
+ module Web
3
+ module RelationshipLoader
4
+ include ThroughRelationshipLoader
5
+
6
+ private
7
+
8
+ def outgoing_relationships
9
+ settings.catalog.outgoing_relationships(@namespace, @relation).filter_map do |relationship|
10
+ outgoing_relationship(relationship)
11
+ end
12
+ end
13
+
14
+ def outgoing_relationship(relationship)
15
+ foreign_key = relationship.foreign_key
16
+ namespace = relationship.target_namespace
17
+ relation = relationship.target_relation
18
+
19
+ key = foreign_key.referenced_columns.zip(foreign_key.columns.map{|column| @record[column] }).to_h
20
+ return if key.values.any?(&:nil?)
21
+
22
+ { foreign_key: foreign_key, namespace: namespace, relation: relation, inferred: relationship.inferred,
23
+ url: relationship_url(namespace, relation, key) }
24
+ end
25
+
26
+ def incoming_relationships
27
+ settings.catalog.incoming_relationships(@namespace, @relation).filter_map do |relationship|
28
+ next if through_junction?(relationship)
29
+
30
+ foreign_key = relationship.foreign_key
31
+ values = foreign_key.referenced_columns.map{|column| @record[column] }
32
+ next if values.any?(&:nil?)
33
+
34
+ related_page(relationship, foreign_key.columns.zip(values).to_h)
35
+ end
36
+ end
37
+
38
+ def related_page(relationship, filters)
39
+ namespace = relationship.source_namespace
40
+ relation = relationship.source_relation
41
+ page = settings.query_service.page(
42
+ namespace: namespace.name, relation: relation.name, filters: filters, per_page: 10
43
+ )
44
+ return if page.rows.empty?
45
+
46
+ { foreign_key: relationship.foreign_key, namespace: namespace, relation: relation,
47
+ inferred: relationship.inferred,
48
+ page: page, url: relation_url(namespace.name, relation.name, filter: equality_filters(filters)) }
49
+ end
50
+
51
+ def relationship_url(namespace, relation, key)
52
+ primary_key = relation.primary_key
53
+ if primary_key&.columns&.sort == key.keys.sort
54
+ return record_url(namespace.name, relation.name, key, primary_key)
55
+ end
56
+
57
+ relation_url(namespace.name, relation.name, filter: equality_filters(key))
58
+ end
59
+
60
+ def equality_filters(values)
61
+ values.transform_values{|value| { eq: value } }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,58 @@
1
+ module RDBr
2
+ module Web
3
+ module SearchValueHelpers
4
+ BIT_TYPES = %i[bit varbit].freeze
5
+ SEARCH_TYPES = %i[tsquery tsvector].freeze
6
+
7
+ def deferred_bit_value?(column)
8
+ type = column.logical_type.to_s.to_sym
9
+ (BIT_TYPES.include?(type) || column.sql_type.to_s.match?(/\bbit(?: varying)?\b/i)) && !vector_column?(column)
10
+ end
11
+
12
+ def deferred_search_value?(column)
13
+ type = column.logical_type.to_s.to_sym
14
+ SEARCH_TYPES.include?(type) || column.sql_type.to_s.match?(/\b(?:tsquery|tsvector)\b/i)
15
+ end
16
+
17
+ def render_bit_value(value)
18
+ text = value.to_s
19
+ length = bit_content(text).length
20
+ unit = length == 1 ? 'bit' : 'bits'
21
+ render_deferred_value(text, qualifier: 'bit-string', summary: "#{length} #{unit}")
22
+ end
23
+
24
+ def render_search_value(column, value)
25
+ type = search_type(column)
26
+ summary = type == :tsvector ? tsvector_summary(value) : tsquery_summary(value)
27
+ render_deferred_value(value, qualifier: 'full-text', summary: summary)
28
+ end
29
+
30
+ private
31
+
32
+ def bit_content(text)
33
+ match = text.strip.match(/\A(?:B)?'([01]*)'\z/i)
34
+ match ? match[1] : text
35
+ end
36
+
37
+ def search_type(column)
38
+ type = column.logical_type.to_s.to_sym
39
+ return type if SEARCH_TYPES.include?(type)
40
+
41
+ column.sql_type.to_s.match?(/\btsvector\b/i) ? :tsvector : :tsquery
42
+ end
43
+
44
+ def tsvector_summary(value)
45
+ count = value.to_s.scan(/'(?:[^']|'')+'/).length
46
+ unit = count == 1 ? 'lexeme' : 'lexemes'
47
+ "#{count} #{unit}"
48
+ end
49
+
50
+ def tsquery_summary(value)
51
+ count = value.to_s.scan(/'(?:[^']|'')+'/).length
52
+ count = value.to_s.scan(/[[:alnum:]_]+/).length if count.zero?
53
+ unit = count == 1 ? 'term' : 'terms'
54
+ "Full-text query · #{count} #{unit}"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,83 @@
1
+ require 'base64'
2
+
3
+ module RDBr
4
+ module Web
5
+ module SpatialValueHelpers
6
+ SPATIAL_TYPES = %i[box circle geography geometry line lseg path point polygon].freeze
7
+ GEOMETRY_NAMES = {
8
+ 'geometrycollection' => 'Geometry collection', 'linestring' => 'Line string',
9
+ 'multilinestring' => 'Multi-line string', 'multipoint' => 'Multi-point',
10
+ 'multipolygon' => 'Multi-polygon', 'point' => 'Point', 'polygon' => 'Polygon'
11
+ }.freeze
12
+
13
+ def spatial_column?(column)
14
+ type = column.logical_type.to_s.downcase.to_sym
15
+ SPATIAL_TYPES.include?(type) || column.sql_type.to_s.match?(spatial_type_pattern)
16
+ end
17
+
18
+ def render_spatial_value(column, value)
19
+ text, summary = spatial_presentation(column, value)
20
+ render_deferred_value(text, qualifier: 'spatial', summary: summary)
21
+ end
22
+
23
+ private
24
+
25
+ def spatial_type_pattern
26
+ /\A(?:box|circle|geography|geometry|line|lseg|path|point|polygon)\b/i
27
+ end
28
+
29
+ def spatial_presentation(column, value)
30
+ return encoded_spatial_presentation(value) if encoded_spatial_value?(value)
31
+ return geo_json_presentation(value) if value.is_a?(Hash)
32
+ return raw_binary_spatial_presentation(value) if raw_binary_spatial_value?(value)
33
+
34
+ spatial_text_presentation(column, value.to_s)
35
+ end
36
+
37
+ def encoded_spatial_value?(value)
38
+ value.is_a?(Hash) && value['encoding'] == 'base64' && value.key?('data')
39
+ end
40
+
41
+ def encoded_spatial_presentation(value)
42
+ payload = value.fetch('data').to_s
43
+ bytes = Base64.strict_decode64(payload).bytesize
44
+ [ payload, "Geometry · #{bytes} bytes · Base64" ]
45
+ rescue ArgumentError
46
+ [ payload, 'Geometry · Base64' ]
47
+ end
48
+
49
+ def geo_json_presentation(value)
50
+ kind = geometry_name(value['type'] || value[:type]) || 'Geometry'
51
+ [ JSON.pretty_generate(value), "#{kind} · GeoJSON" ]
52
+ end
53
+
54
+ def raw_binary_spatial_value?(value)
55
+ value.is_a?(String) && value.encoding == Encoding::BINARY
56
+ end
57
+
58
+ def raw_binary_spatial_presentation(value)
59
+ [ Base64.strict_encode64(value), "Geometry · #{value.bytesize} bytes · Base64" ]
60
+ end
61
+
62
+ def spatial_text_presentation(column, text)
63
+ srid = text.match(/\ASRID=(\d+);/i)&.[](1)
64
+ body = text.sub(/\ASRID=\d+;/i, '')
65
+ kind = geometry_name(body[/\A([[:alpha:]]+)/, 1]) || geometry_name(column.logical_type) || 'Geometry'
66
+ suffix = srid ? " · SRID #{srid}" : spatial_encoding_suffix(body)
67
+ [ text, "#{kind}#{suffix}" ]
68
+ end
69
+
70
+ def geometry_name(value)
71
+ text = value.to_s
72
+ GEOMETRY_NAMES[text.downcase] || (text.capitalize unless text.empty?)
73
+ end
74
+
75
+ def spatial_encoding_suffix(text)
76
+ hex = text.delete_prefix('\\x')
77
+ return '' unless hex.match?(/\A[[:xdigit:]]+\z/) && hex.length.even?
78
+
79
+ " · #{hex.length / 2} bytes · hex"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,90 @@
1
+ module RDBr
2
+ module Web
3
+ module ThroughRelationshipLoader
4
+ private
5
+
6
+ def through_relationships
7
+ settings.catalog.through_relationships(@namespace, @relation).filter_map do |relationship|
8
+ through_relationship(relationship)
9
+ end
10
+ end
11
+
12
+ def through_relationship(relationship)
13
+ source_key = relationship.source_foreign_key
14
+ values = source_key.referenced_columns.map{|column| @record[column] }
15
+ return if values.any?(&:nil?)
16
+
17
+ filters = source_key.columns.zip(values).to_h
18
+ junction_page = settings.query_service.page(
19
+ namespace: relationship.junction_namespace.name, relation: relationship.junction_relation.name,
20
+ filters: filters, per_page: 10
21
+ )
22
+
23
+ { relationship: relationship, junction_page: junction_page,
24
+ junction_url: relation_url(relationship.junction_namespace.name, relationship.junction_relation.name,
25
+ filter: equality_filters(filters)),
26
+ targets: through_targets(relationship, junction_page.rows) }
27
+ end
28
+
29
+ def through_junction?(direct_relationship)
30
+ @through_relationships.any? do |entry|
31
+ through = entry.fetch(:relationship)
32
+ through.junction_namespace == direct_relationship.source_namespace &&
33
+ through.junction_relation == direct_relationship.source_relation
34
+ end
35
+ end
36
+
37
+ def through_targets(relationship, rows)
38
+ foreign_key = relationship.target_foreign_key
39
+ keyed_rows = through_target_keys(foreign_key, rows)
40
+ targets = lookup_through_targets(relationship, keyed_rows)
41
+ targets_by_key = index_through_targets(foreign_key, targets)
42
+
43
+ keyed_rows.map do |junction_row, values|
44
+ build_through_target(relationship, junction_row, values, targets_by_key[values.values])
45
+ end
46
+ end
47
+
48
+ def through_target_keys(foreign_key, rows)
49
+ rows.filter_map do |row|
50
+ values = foreign_key.referenced_columns.zip(
51
+ foreign_key.columns.map{|column| row[column] }
52
+ ).to_h
53
+ [ row, values ] unless values.values.any?(&:nil?)
54
+ end
55
+ end
56
+
57
+ def lookup_through_targets(relationship, keyed_rows)
58
+ settings.query_service.lookup(
59
+ namespace: relationship.target_namespace.name, relation: relationship.target_relation.name,
60
+ keys: keyed_rows.map(&:last)
61
+ )
62
+ end
63
+
64
+ def index_through_targets(foreign_key, targets)
65
+ targets.index_by do |target|
66
+ foreign_key.referenced_columns.map{|column| target[column] }
67
+ end
68
+ end
69
+
70
+ def build_through_target(relationship, junction_row, values, target)
71
+ {
72
+ row: target,
73
+ url: target && relationship_url(relationship.target_namespace, relationship.target_relation, values),
74
+ junction_url: junction_record_url(relationship, junction_row)
75
+ }
76
+ end
77
+
78
+ def junction_record_url(relationship, row)
79
+ namespace = relationship.junction_namespace
80
+ relation = relationship.junction_relation
81
+ primary_key = relation.primary_key
82
+ return record_url(namespace.name, relation.name, row, primary_key) if primary_key
83
+
84
+ columns = relationship.source_foreign_key.columns | relationship.target_foreign_key.columns
85
+ values = columns.to_h{|column| [ column, row[column]] }
86
+ relation_url(namespace.name, relation.name, filter: equality_filters(values))
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,59 @@
1
+ module RDBr
2
+ module Web
3
+ module VectorValueHelpers
4
+ VECTOR_TYPES = %w[vector halfvec sparsevec].freeze
5
+ BIT_TYPES = %w[bit varbit].freeze
6
+ VECTOR_NAMES = %w[embedding embeddings vector vectors].freeze
7
+
8
+ def vector_column?(column)
9
+ type = column.logical_type.to_s.downcase
10
+ sql_type = column.sql_type.to_s.downcase
11
+ VECTOR_TYPES.include?(type) || sql_type.match?(/\b(?:vector|halfvec|sparsevec)\b/) ||
12
+ binary_vector_column?(column, type)
13
+ end
14
+
15
+ def render_vector_value(column, value)
16
+ render_deferred_value(
17
+ vector_text(value), qualifier: 'vector', summary: vector_summary(column, value)
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ def binary_vector_column?(column, type)
24
+ return false unless BIT_TYPES.include?(type)
25
+
26
+ name_parts = column.name.gsub(/([a-z\d])([A-Z])/, '\\1_\\2').downcase.split(/[^a-z0-9]+/)
27
+ (name_parts & VECTOR_NAMES).any?
28
+ end
29
+
30
+ def vector_summary(column, value)
31
+ dimensions = vector_dimensions(column, value)
32
+ return 'Vector' unless dimensions
33
+
34
+ "#{dimensions}-dimensional vector"
35
+ end
36
+
37
+ def vector_dimensions(column, value)
38
+ declared = column.sql_type.to_s.match(/\b(?:vector|halfvec|bit|varbit)\s*\(\s*(\d+)\s*\)/i)
39
+ return Integer(declared[1]) if declared
40
+ return value.length if value.is_a?(Array)
41
+
42
+ text = value.to_s.strip
43
+ sparse_dimensions = text.match(%r{/\s*(\d+)\s*\z})
44
+ return Integer(sparse_dimensions[1]) if sparse_dimensions
45
+ if BIT_TYPES.include?(column.logical_type.to_s.downcase)
46
+ return text.delete_prefix("B'").delete_suffix("'").length
47
+ end
48
+ return unless text.start_with?('[') && text.end_with?(']')
49
+
50
+ body = text[1...-1].strip
51
+ body.empty? ? 0 : body.count(',') + 1
52
+ end
53
+
54
+ def vector_text(value)
55
+ value.is_a?(Array) ? JSON.generate(value) : value.to_s
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/rdbr.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'active_record'
2
+ require 'haml'
3
+ require 'json'
4
+ require 'sinatra/base'
5
+ require 'time'
6
+
7
+ require_relative 'rdbr/version'
8
+ require_relative 'rdbr/metadata'
9
+ require_relative 'rdbr/catalog'
10
+ require_relative 'rdbr/access'
11
+ require_relative 'rdbr/query'
12
+ require_relative 'rdbr/presentation/record_identity'
13
+ require_relative 'rdbr/browser_launcher'
14
+ require_relative 'rdbr/cli_options'
15
+ require_relative 'rdbr/cli'
16
+ require_relative 'rdbr/web/helpers'
17
+ require_relative 'rdbr/web/column_preferences'
18
+ require_relative 'rdbr/web/json_page_stream'
19
+ require_relative 'rdbr/web/through_relationship_loader'
20
+ require_relative 'rdbr/web/relationship_loader'
21
+ require_relative 'rdbr/web/database_graph'
22
+ require_relative 'rdbr/web/application'
23
+
24
+ module RDBr
25
+ end
metadata ADDED
@@ -0,0 +1,298 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rdbr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Blake
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '8.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
32
+ - !ruby/object:Gem::Dependency
33
+ name: haml
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.0'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '8'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '7.0'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '8'
52
+ - !ruby/object:Gem::Dependency
53
+ name: puma
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '7.0'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '9'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '7.0'
69
+ - - "<"
70
+ - !ruby/object:Gem::Version
71
+ version: '9'
72
+ - !ruby/object:Gem::Dependency
73
+ name: rackup
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '2.2'
79
+ - - "<"
80
+ - !ruby/object:Gem::Version
81
+ version: '3'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '2.2'
89
+ - - "<"
90
+ - !ruby/object:Gem::Version
91
+ version: '3'
92
+ - !ruby/object:Gem::Dependency
93
+ name: sinatra
94
+ requirement: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '4.1'
99
+ - - "<"
100
+ - !ruby/object:Gem::Version
101
+ version: '5'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '4.1'
109
+ - - "<"
110
+ - !ruby/object:Gem::Version
111
+ version: '5'
112
+ - !ruby/object:Gem::Dependency
113
+ name: minitest
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: rack-test
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: rake
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ - !ruby/object:Gem::Dependency
155
+ name: rubocop
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: rubocop-rails
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ description: RDBr discovers relational database structure and presents a generic,
183
+ read-only web interface.
184
+ email:
185
+ - blakethomasandrew@gmail.com
186
+ executables:
187
+ - rdbr
188
+ extensions: []
189
+ extra_rdoc_files: []
190
+ files:
191
+ - CHANGELOG.md
192
+ - LICENSE.txt
193
+ - README.md
194
+ - app/public/application.js
195
+ - app/public/database-diagram.js
196
+ - app/public/styles.css
197
+ - app/views/data.haml
198
+ - app/views/group.haml
199
+ - app/views/index.haml
200
+ - app/views/layout.haml
201
+ - app/views/record.haml
202
+ - demo/README.md
203
+ - demo/compose.yml
204
+ - demo/seed.rb
205
+ - demo/smoke.rb
206
+ - docs/RELEASING.md
207
+ - docs/VALUE_PRESENTATION.md
208
+ - exe/rdbr
209
+ - lib/rdbr.rb
210
+ - lib/rdbr/access.rb
211
+ - lib/rdbr/access/read_only.rb
212
+ - lib/rdbr/browser_launcher.rb
213
+ - lib/rdbr/catalog.rb
214
+ - lib/rdbr/catalog/connection.rb
215
+ - lib/rdbr/catalog/errors.rb
216
+ - lib/rdbr/catalog/mysql.rb
217
+ - lib/rdbr/catalog/mysql/queries.rb
218
+ - lib/rdbr/catalog/postgresql.rb
219
+ - lib/rdbr/catalog/postgresql/queries.rb
220
+ - lib/rdbr/catalog/reader.rb
221
+ - lib/rdbr/catalog/serializer.rb
222
+ - lib/rdbr/catalog/sqlite.rb
223
+ - lib/rdbr/cli.rb
224
+ - lib/rdbr/cli_options.rb
225
+ - lib/rdbr/metadata.rb
226
+ - lib/rdbr/metadata/column.rb
227
+ - lib/rdbr/metadata/constraints.rb
228
+ - lib/rdbr/metadata/database.rb
229
+ - lib/rdbr/metadata/index.rb
230
+ - lib/rdbr/metadata/keys.rb
231
+ - lib/rdbr/metadata/namespace.rb
232
+ - lib/rdbr/metadata/relation.rb
233
+ - lib/rdbr/metadata/relationship_inference.rb
234
+ - lib/rdbr/metadata/relationships.rb
235
+ - lib/rdbr/metadata/values.rb
236
+ - lib/rdbr/presentation/record_identity.rb
237
+ - lib/rdbr/query.rb
238
+ - lib/rdbr/query/connection.rb
239
+ - lib/rdbr/query/errors.rb
240
+ - lib/rdbr/query/lookup.rb
241
+ - lib/rdbr/query/mysql.rb
242
+ - lib/rdbr/query/page.rb
243
+ - lib/rdbr/query/postgresql.rb
244
+ - lib/rdbr/query/postgresql_dialect.rb
245
+ - lib/rdbr/query/relational.rb
246
+ - lib/rdbr/query/sqlite.rb
247
+ - lib/rdbr/query/type_support.rb
248
+ - lib/rdbr/query/value_limiter.rb
249
+ - lib/rdbr/version.rb
250
+ - lib/rdbr/web/application.rb
251
+ - lib/rdbr/web/array_value_helpers.rb
252
+ - lib/rdbr/web/binary_value_helpers.rb
253
+ - lib/rdbr/web/column_preferences.rb
254
+ - lib/rdbr/web/database_graph.rb
255
+ - lib/rdbr/web/deferred_column_value_helpers.rb
256
+ - lib/rdbr/web/deferred_value_helpers.rb
257
+ - lib/rdbr/web/document_value_helpers.rb
258
+ - lib/rdbr/web/filter_presentation_helpers.rb
259
+ - lib/rdbr/web/helpers.rb
260
+ - lib/rdbr/web/identifier_value_helpers.rb
261
+ - lib/rdbr/web/json_page_stream.rb
262
+ - lib/rdbr/web/navigation_helpers.rb
263
+ - lib/rdbr/web/presentation_helpers.rb
264
+ - lib/rdbr/web/range_value_helpers.rb
265
+ - lib/rdbr/web/record_value_helpers.rb
266
+ - lib/rdbr/web/related_value_helpers.rb
267
+ - lib/rdbr/web/relationship_loader.rb
268
+ - lib/rdbr/web/search_value_helpers.rb
269
+ - lib/rdbr/web/spatial_value_helpers.rb
270
+ - lib/rdbr/web/through_relationship_loader.rb
271
+ - lib/rdbr/web/vector_value_helpers.rb
272
+ homepage: https://github.com/andrewtblake/rdbr
273
+ licenses:
274
+ - MIT
275
+ metadata:
276
+ source_code_uri: https://github.com/andrewtblake/rdbr
277
+ changelog_uri: https://github.com/andrewtblake/rdbr/blob/main/CHANGELOG.md
278
+ documentation_uri: https://github.com/andrewtblake/rdbr#readme
279
+ bug_tracker_uri: https://github.com/andrewtblake/rdbr/issues
280
+ rubygems_mfa_required: 'true'
281
+ rdoc_options: []
282
+ require_paths:
283
+ - lib
284
+ required_ruby_version: !ruby/object:Gem::Requirement
285
+ requirements:
286
+ - - ">="
287
+ - !ruby/object:Gem::Version
288
+ version: 3.2.0
289
+ required_rubygems_version: !ruby/object:Gem::Requirement
290
+ requirements:
291
+ - - ">="
292
+ - !ruby/object:Gem::Version
293
+ version: '0'
294
+ requirements: []
295
+ rubygems_version: 4.0.16
296
+ specification_version: 4
297
+ summary: A standalone, read-only browser for relational databases
298
+ test_files: []