simple-sql 0.4.22 → 0.4.23

Sign up to get free protection for your applications and to get access to all the features.
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