simple-sql 0.4.22 → 0.4.23

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58caff3c654cc1f068e15d93f2d356fde5598e159ddd2589aeb5e5588a1e8c16
4
- data.tar.gz: d9d06f994b53824b937872c548601c025a93060c917602a73e99d8a8ab24dbdd
3
+ metadata.gz: 27cc3b86bf4e72218ecc962cea6700d9a58e6a8ef594e15bfad16125bba792aa
4
+ data.tar.gz: c4642ed511a3dd94511d6a56b9c22e5bce830e15a6ecb3e2ed3a74d192253e56
5
5
  SHA512:
6
- metadata.gz: af6166f4600345b8151092e3f2198f89defb2d64365a3e0d616b2da86ef87a73ba7b6ed360e3c4dd4371569afa70e210ef72bdbbabd25e5b87f685cc0d7c99ef
7
- data.tar.gz: e04ef18bd368c7e2e404daf84767204335de2f85ee22594d56bd37415b085e6dc78417cd46d5e085e134493d2cb0d09318da8665a8695a1333a6e89a7c24af7c
6
+ metadata.gz: 886016b412df3e5ad6d3eead65a1c1c81fe8c6eb98d868f2ae479a6c18b333d68f7661a21e17a566e7cfc151d8c844fca297c86bb7170ca423ff3d97a6bace12
7
+ data.tar.gz: a651642b76dea15fe5d56c2311576a24d2992aa07d255aab244735c392fe7d9465a93aa255479ba2544e115162bf54b15378d718af40849d3645fbe8338eb051
data/.rubocop.yml CHANGED
@@ -66,3 +66,9 @@ Style/ClassVars:
66
66
 
67
67
  Style/ConditionalAssignment:
68
68
  Enabled: false
69
+
70
+ Style/IfUnlessModifier:
71
+ Enabled: false
72
+
73
+ Style/PerlBackrefs:
74
+ Enabled: false
data/Makefile ADDED
@@ -0,0 +1,5 @@
1
+ stats:
2
+ @scripts/stats lib/simple/sql
3
+ @scripts/stats spec/simple/sql
4
+ @scripts/stats lib/simple/store
5
+ @scripts/stats spec/simple/store
data/bin/console CHANGED
@@ -1,17 +1,36 @@
1
1
  #!/usr/bin/env ruby
2
2
  $: << "lib"
3
3
  require "simple/sql"
4
+ require "simple/store"
4
5
 
5
6
  SQL = Simple::SQL
6
7
  SQL.connect!
7
8
 
8
- def reload!
9
- $VERBOSE = nil
10
- Dir.glob("lib/simple/sql/**/*.rb").sort.each do |path|
11
- STDERR.puts path
9
+ module Reload
10
+ extend self
11
+
12
+ def load_file(path)
13
+ STDERR.puts "Loading #{path}\n"
12
14
  load path
13
- end
15
+ end
16
+
17
+ def load_dir(dir)
18
+ Dir.glob("#{dir}/**/*.rb").sort.each_with_index do |path, idx|
19
+ load_file path
20
+ end
21
+ end
14
22
  end
15
23
 
24
+ $VERBOSE = nil
25
+
26
+ def reload!
27
+ Reload.load_dir "lib/simple"
28
+ Reload.load_file "config/console-init.rb"
29
+ end
30
+
31
+ Reload.load_file "config/console-init.rb"
32
+
16
33
  require "irb"
34
+ require "irb/completion"
35
+
17
36
  IRB.start
data/bin/pg ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ require 'yaml'
3
+
4
+ env = ENV["POSTJOB_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
5
+
6
+ configs = YAML.load_file "config/database.yml"
7
+ config = configs.fetch(env) { configs.fetch("defaults") }
8
+
9
+ ENV["PGHOST"] = config["host"]
10
+ ENV["PGPORT"] = config["port"] && config["port"].to_s
11
+ ENV["PGUSER"] = config["username"]
12
+ ENV["PGPASSWORD"] = config["password"]
13
+
14
+ system "psql", "-d", config.fetch("database")
@@ -0,0 +1 @@
1
+ # This file is loaded when running bin/console.
data/lib/simple/sql.rb CHANGED
@@ -21,7 +21,7 @@ module Simple
21
21
  module SQL
22
22
  extend self
23
23
  extend Forwardable
24
- delegate [:ask, :all, :each, :exec, :print] => :connection
24
+ delegate [:ask, :all, :each, :exec, :locked, :print] => :connection
25
25
  delegate [:transaction, :wait_for_notify] => :connection
26
26
 
27
27
  delegate [:logger, :logger=] => ::Simple::SQL::Logging
@@ -14,6 +14,7 @@ module Simple::SQL::Connection
14
14
  Logging.info "Connecting to #{database_url}"
15
15
 
16
16
  raw_connection = PG::Connection.new(config)
17
+ raw_connection.set_notice_processor { |message| Logging.info(message) }
17
18
  PgConnection.new(raw_connection)
18
19
  end
19
20
 
@@ -1,5 +1,3 @@
1
- # rubocop:disable Style/IfUnlessModifier
2
-
3
1
  # This module implements an adapter between the Simple::SQL interface
4
2
  # (i.e. ask, all, first, transaction) and a raw connection.
5
3
  #
@@ -83,6 +81,21 @@ module Simple::SQL::ConnectionAdapter
83
81
  end
84
82
  end
85
83
 
84
+ # Executes a block, usually of db insert code, while holding an
85
+ # advisory lock.
86
+ #
87
+ # Examples:
88
+ #
89
+ # - <tt>Simple::SQL.locked(4711) { puts 'do work while locked' }
90
+ def locked(lock_id)
91
+ begin
92
+ ask("SELECT pg_advisory_lock(#{lock_id})")
93
+ yield
94
+ ensure
95
+ ask("SELECT pg_advisory_unlock(#{lock_id})")
96
+ end
97
+ end
98
+
86
99
  private
87
100
 
88
101
  Encoder = ::Simple::SQL::Helpers::Encoder
@@ -2,7 +2,11 @@
2
2
 
3
3
  module Simple
4
4
  module SQL
5
- class Fragment < Struct.new(:to_sql)
5
+ unless defined?(Fragment)
6
+
7
+ class Fragment < Struct.new(:to_sql)
8
+ end
9
+
6
10
  end
7
11
 
8
12
  def fragment(str)
@@ -0,0 +1,150 @@
1
+ module Simple
2
+ module SQL
3
+ module Helpers
4
+ end
5
+ end
6
+ end
7
+
8
+ class Simple::SQL::Helpers::Immutable
9
+ SELF = self
10
+
11
+ # turns an object, which can be a hash or array of hashes, arrays, and scalars
12
+ # into an object which you can use to access with dot methods.
13
+ def self.create(object, max_depth = 5)
14
+ case object
15
+ when Array
16
+ raise ArgumentError, "Object nested too deep (or inner loop?)" if max_depth < 0
17
+ object.map { |obj| create obj, max_depth - 1 }
18
+ when Hash
19
+ new(object)
20
+ else
21
+ object
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def initialize(hsh)
28
+ @hsh = hsh
29
+ end
30
+
31
+ def method_missing(sym, *args, &block)
32
+ if args.empty? && !block
33
+ begin
34
+ value = @hsh.fetch(sym.to_sym) { @hsh.fetch(sym.to_s) }
35
+ return SELF.create(value)
36
+ rescue KeyError
37
+ # STDERR.puts "Missing attribute #{sym} for Immutable w/#{@hsh.inspect}"
38
+ nil
39
+ end
40
+ end
41
+
42
+ super
43
+ end
44
+
45
+ public
46
+
47
+ def respond_to_missing?(method_name, include_private = false)
48
+ @hsh.key?(method_name.to_sym) ||
49
+ @hsh.key?(method_name.to_s) ||
50
+ super
51
+ end
52
+
53
+ def inspect
54
+ "<Immutable: #{@hsh.inspect}>"
55
+ end
56
+
57
+ def respond_to?(sym)
58
+ super || @hsh.key?(sym.to_s) || @hsh.key?(sym.to_sym)
59
+ end
60
+
61
+ def ==(other)
62
+ @hsh == other
63
+ end
64
+ end
65
+
66
+ if $PROGRAM_NAME == __FILE__
67
+
68
+ require "test-unit"
69
+
70
+ class Simple::SQL::Helpers::Immutable::TestCase < Test::Unit::TestCase
71
+ Immutable = ::Simple::SQL::Helpers::Immutable
72
+
73
+ def hsh
74
+ {
75
+ a: "a-value",
76
+ "b": "b-value",
77
+ "child": {
78
+ name: "childname",
79
+ grandchild: {
80
+ name: "grandchildname"
81
+ }
82
+ },
83
+ "children": [
84
+ "anna",
85
+ "arthur",
86
+ {
87
+ action: {
88
+ keep_your_mouth_shut: true
89
+ }
90
+ }
91
+ ]
92
+ }
93
+ end
94
+
95
+ def immutable
96
+ Immutable.create hsh
97
+ end
98
+
99
+ def test_hash_access
100
+ assert_equal "a-value", immutable.a
101
+ assert_equal "b-value", immutable.b
102
+ end
103
+
104
+ def test_comparison
105
+ immutable = Immutable.create hsh
106
+
107
+ assert_equal immutable, hsh
108
+ assert_not_equal({}, immutable)
109
+ end
110
+
111
+ def test_child_access
112
+ child = immutable.child
113
+ assert_kind_of(Immutable, child)
114
+ assert_equal "childname", immutable.child.name
115
+ assert_equal "grandchildname", immutable.child.grandchild.name
116
+ end
117
+
118
+ def test_array_access
119
+ assert_kind_of(Array, immutable.children)
120
+ assert_equal 3, immutable.children.length
121
+ assert_equal "anna", immutable.children[0]
122
+
123
+ assert_kind_of(Immutable, immutable.children[2])
124
+ assert_equal true, immutable.children[2].action.keep_your_mouth_shut
125
+ end
126
+
127
+ def test_base_class
128
+ assert_nothing_raised do
129
+ immutable.object_id
130
+ end
131
+ end
132
+
133
+ def test_missing_keys
134
+ assert_raise(NoMethodError) do
135
+ immutable.foo
136
+ end
137
+ end
138
+
139
+ def test_skip_when_args_or_block
140
+ assert_raise(NoMethodError) do
141
+ immutable.a(1, 2, 3)
142
+ end
143
+ assert_raise(NoMethodError) do
144
+ immutable.a { :dummy }
145
+ end
146
+ end
147
+ end
148
+
149
+ # rubocop:enable Style/ClassAndModuleChildren
150
+ end
@@ -1,15 +1,21 @@
1
+ require_relative "./immutable"
2
+
1
3
  module Simple::SQL::Helpers::RowConverter
2
4
  SELF = self
3
5
 
4
6
  # returns an array of converted records
5
- def self.convert_row(records, into:, associations: nil)
7
+ def self.convert_row(records, into:, associations: nil, fq_table_name: nil)
6
8
  hsh = records.first
7
9
  return records unless hsh
8
10
 
9
11
  converter = if into == :struct
10
12
  StructConverter.for(attributes: hsh.keys, associations: associations)
13
+ elsif into == :immutable
14
+ ImmutableConverter.new(type: into, associations: associations)
15
+ elsif into.respond_to?(:new_from_row)
16
+ TypeConverter2.new(type: into, associations: associations, fq_table_name: fq_table_name)
11
17
  else
12
- TypeConverter.for(type: into, associations: associations)
18
+ TypeConverter.new(type: into, associations: associations)
13
19
  end
14
20
 
15
21
  records.map { |record| converter.convert_row(record) }
@@ -21,10 +27,6 @@ module Simple::SQL::Helpers::RowConverter
21
27
  end
22
28
 
23
29
  class TypeConverter #:nodoc:
24
- def self.for(type:, associations:)
25
- new(type: type, associations: associations)
26
- end
27
-
28
30
  def initialize(type:, associations:)
29
31
  @type = type
30
32
  @associations = associations
@@ -32,6 +34,10 @@ module Simple::SQL::Helpers::RowConverter
32
34
 
33
35
  def convert_row(hsh)
34
36
  hsh = convert_associations(hsh) if @associations
37
+ build_row_in_target_type hsh
38
+ end
39
+
40
+ def build_row_in_target_type(hsh)
35
41
  @type.new hsh
36
42
  end
37
43
 
@@ -50,6 +56,26 @@ module Simple::SQL::Helpers::RowConverter
50
56
  end
51
57
  end
52
58
 
59
+ class ImmutableConverter < TypeConverter #:nodoc:
60
+ Immutable = ::Simple::SQL::Helpers::Immutable
61
+
62
+ def build_row_in_target_type(hsh)
63
+ Immutable.create hsh
64
+ end
65
+ end
66
+
67
+ class TypeConverter2 < TypeConverter #:nodoc:
68
+ def initialize(type:, associations:, fq_table_name:)
69
+ super(type: type, associations: associations)
70
+ @fq_table_name = fq_table_name
71
+ end
72
+
73
+ def convert_row(hsh)
74
+ hsh = convert_associations(hsh) if @associations
75
+ @type.new_from_row hsh, fq_table_name: @fq_table_name
76
+ end
77
+ end
78
+
53
79
  class StructConverter # :nodoc:
54
80
  def self.for(attributes:, associations:)
55
81
  @cache ||= {}
@@ -40,6 +40,8 @@ module Simple
40
40
  # - columns - name of columns, as Array[String] or Array[Symbol]
41
41
  #
42
42
  def initialize(table_name:, columns:, on_conflict:, into:)
43
+ raise ArgumentError, "Cannot insert a record without attributes" if columns.empty?
44
+
43
45
  @columns = columns
44
46
  @into = into
45
47
 
@@ -54,7 +56,7 @@ module Simple
54
56
  cols += timestamp_columns
55
57
  vals += timestamp_columns.map { "now()" }
56
58
 
57
- returning = into ? '*' : "id"
59
+ returning = into ? "*" : "id"
58
60
 
59
61
  @sql = "INSERT INTO #{table_name} (#{cols.join(',')}) VALUES(#{vals.join(',')}) #{confict_handling(on_conflict)} RETURNING #{returning}"
60
62
  end
@@ -46,6 +46,13 @@ module Simple
46
46
  end
47
47
 
48
48
  def column_info(table_name)
49
+ @column_info ||= {}
50
+ @column_info[table_name] ||= _column_info(table_name)
51
+ end
52
+
53
+ private
54
+
55
+ def _column_info(table_name)
49
56
  schema, table_name = parse_table_name(table_name)
50
57
  recs = all <<~SQL, schema, table_name, into: Hash
51
58
  SELECT
@@ -58,8 +65,6 @@ module Simple
58
65
  records_by_attr(recs, :column_name)
59
66
  end
60
67
 
61
- private
62
-
63
68
  def parse_table_name(table_name)
64
69
  p1, p2 = table_name.split(".", 2)
65
70
  if p2
@@ -75,6 +80,24 @@ module Simple
75
80
  hsh.update record[attr] => OpenStruct.new(record)
76
81
  end
77
82
  end
83
+
84
+ public
85
+
86
+ def lookup_pg_class(oid)
87
+ @pg_classes ||= {}
88
+ @pg_classes[oid] ||= _lookup_pg_class(oid)
89
+ end
90
+
91
+ private
92
+
93
+ def _lookup_pg_class(oid)
94
+ ::Simple::SQL.ask <<~SQL, oid
95
+ SELECT nspname AS schema, relname AS host_table
96
+ FROM pg_class
97
+ JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace
98
+ WHERE pg_class.oid=$1
99
+ SQL
100
+ end
78
101
  end
79
102
  end
80
103
  end
@@ -1,8 +1,11 @@
1
1
  # xrubocop:disable Metrics/ParameterLists
2
2
 
3
3
  require_relative "association_loader"
4
+ require "simple/sql/reflection"
4
5
 
5
6
  class ::Simple::SQL::Result::Records < ::Simple::SQL::Result
7
+ Reflection = ::Simple::SQL::Reflection
8
+
6
9
  def initialize(records, target_type:, pg_source_oid:) # :nodoc:
7
10
  expect! records.first => Hash unless records.empty?
8
11
 
@@ -45,12 +48,9 @@ class ::Simple::SQL::Result::Records < ::Simple::SQL::Result
45
48
  expect! as => [nil, Symbol]
46
49
 
47
50
  # resolve oid into table and schema name.
48
- schema, host_table = ::Simple::SQL.ask <<~SQL, @pg_source_oid
49
- SELECT nspname AS schema, relname AS host_table
50
- FROM pg_class
51
- JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace
52
- WHERE pg_class.oid=$1
53
- SQL
51
+ #
52
+ # [TODO] is this still correct?
53
+ schema, host_table = Reflection.lookup_pg_class @pg_source_oid
54
54
 
55
55
  AssociationLoader.preload @hash_records, association,
56
56
  host_table: host_table, schema: schema, as: as,
@@ -68,7 +68,12 @@ class ::Simple::SQL::Result::Records < ::Simple::SQL::Result
68
68
 
69
69
  def materialize
70
70
  records = @hash_records
71
- records = RowConverter.convert_row(records, associations: @associations, into: @target_type) if @target_type != Hash
71
+ if @target_type != Hash
72
+ schema, host_table = Reflection.lookup_pg_class(@pg_source_oid)
73
+ records = RowConverter.convert_row(records, associations: @associations,
74
+ into: @target_type,
75
+ fq_table_name: "#{schema}.#{host_table}")
76
+ end
72
77
  replace(records)
73
78
  end
74
79
  end
@@ -1,4 +1,3 @@
1
- # rubocop:disable Style/IfUnlessModifier
2
1
  # rubocop:disable Style/GuardClause
3
2
 
4
3
  class Simple::SQL::Scope
@@ -1,5 +1,3 @@
1
- # rubocop:disable Style/IfUnlessModifier
2
-
3
1
  # private
4
2
  module Simple::SQL::SimpleTransactions
5
3
  def tx_nesting_level
@@ -1,5 +1,5 @@
1
1
  module Simple
2
2
  module SQL
3
- VERSION = "0.4.22"
3
+ VERSION = "0.4.23"
4
4
  end
5
5
  end
data/scripts/stats ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+ printf "=== $1\n"
3
+ (
4
+ if [ -f $1.rb ]; then
5
+ cloc $(find $1/ -name *rb) $1.rb
6
+ else
7
+ cloc $(find $1/ -name *rb)
8
+ fi
9
+ ) |
10
+ grep -E 'Language|Ruby' | sed 's-Language- -'
11
+ printf "\n"
@@ -0,0 +1,20 @@
1
+ require "spec_helper"
2
+
3
+ describe "Simple::SQL.locked" do
4
+ xit 'acquires and releases an advisory lock' do # pending: "This code was manually tested"
5
+ one = Simple::SQL.locked(4711) do
6
+ Simple::SQL.ask "SELECT 1"
7
+ end
8
+
9
+ expect(one).to eq(1)
10
+ end
11
+
12
+ xit 'releases the lock after an exception' do # pending: "This code was manually tested"
13
+ begin
14
+ Simple::SQL.locked(4711) do
15
+ raise "HU"
16
+ end
17
+ rescue
18
+ end
19
+ end
20
+ end
data/spec/spec_helper.rb CHANGED
@@ -21,7 +21,8 @@ end
21
21
  SQL = Simple::SQL
22
22
  USER_COUNT = 2
23
23
 
24
- ActiveRecord::Base.logger.level = Logger::INFO
24
+ ActiveRecord::Base.logger.level = Logger::DEBUG
25
+ Simple::SQL.logger.level = Logger::DEBUG
25
26
 
26
27
  RSpec.configure do |config|
27
28
  config.run_all_when_everything_filtered = true
@@ -30,6 +31,10 @@ RSpec.configure do |config|
30
31
  config.order = "random"
31
32
  config.example_status_persistence_file_path = ".rspec.data"
32
33
 
34
+ config.backtrace_exclusion_patterns << /spec\/support/
35
+ config.backtrace_exclusion_patterns << /spec_helper/
36
+ config.backtrace_exclusion_patterns << /database_cleaner/
37
+
33
38
  config.around(:each) do |example|
34
39
  Simple::SQL.ask "TRUNCATE TABLE users, unique_users, organizations RESTART IDENTITY CASCADE"
35
40
  example.run
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.22
4
+ version: 0.4.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-11-22 00:00:00.000000000 Z
12
+ date: 2018-11-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pg_array_parser
@@ -175,10 +175,13 @@ files:
175
175
  - ".rubocop.yml"
176
176
  - ".tm_properties"
177
177
  - Gemfile
178
+ - Makefile
178
179
  - README.md
179
180
  - Rakefile
180
181
  - bin/console
182
+ - bin/pg
181
183
  - bin/rake
184
+ - config/console-init.rb
182
185
  - config/database.yml
183
186
  - lib/simple-sql.rb
184
187
  - lib/simple/sql.rb
@@ -189,6 +192,7 @@ files:
189
192
  - lib/simple/sql/helpers.rb
190
193
  - lib/simple/sql/helpers/decoder.rb
191
194
  - lib/simple/sql/helpers/encoder.rb
195
+ - lib/simple/sql/helpers/immutable.rb
192
196
  - lib/simple/sql/helpers/row_converter.rb
193
197
  - lib/simple/sql/insert.rb
194
198
  - lib/simple/sql/logging.rb
@@ -203,6 +207,7 @@ files:
203
207
  - lib/simple/sql/simple_transactions.rb
204
208
  - lib/simple/sql/version.rb
205
209
  - log/.gitkeep
210
+ - scripts/stats
206
211
  - scripts/watch
207
212
  - simple-sql.gemspec
208
213
  - spec/manual/threadtest.rb
@@ -217,6 +222,7 @@ files:
217
222
  - spec/simple/sql/reflection_spec.rb
218
223
  - spec/simple/sql/scope_spec.rb
219
224
  - spec/simple/sql/version_spec.rb
225
+ - spec/simple/sql_locked_spec.rb
220
226
  - spec/spec_helper.rb
221
227
  - spec/support/001_database.rb
222
228
  - spec/support/002_database_cleaner.rb
@@ -260,6 +266,7 @@ test_files:
260
266
  - spec/simple/sql/reflection_spec.rb
261
267
  - spec/simple/sql/scope_spec.rb
262
268
  - spec/simple/sql/version_spec.rb
269
+ - spec/simple/sql_locked_spec.rb
263
270
  - spec/spec_helper.rb
264
271
  - spec/support/001_database.rb
265
272
  - spec/support/002_database_cleaner.rb