torque-postgresql 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 (33) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +31 -0
  4. data/lib/torque/postgresql/adapter/database_statements.rb +103 -0
  5. data/lib/torque/postgresql/adapter/oid/array.rb +19 -0
  6. data/lib/torque/postgresql/adapter/oid/enum.rb +46 -0
  7. data/lib/torque/postgresql/adapter/oid/interval.rb +94 -0
  8. data/lib/torque/postgresql/adapter/oid.rb +15 -0
  9. data/lib/torque/postgresql/adapter/quoting.rb +23 -0
  10. data/lib/torque/postgresql/adapter/schema_definitions.rb +28 -0
  11. data/lib/torque/postgresql/adapter/schema_dumper.rb +31 -0
  12. data/lib/torque/postgresql/adapter/schema_statements.rb +89 -0
  13. data/lib/torque/postgresql/adapter.rb +29 -0
  14. data/lib/torque/postgresql/attributes/builder/enum.rb +151 -0
  15. data/lib/torque/postgresql/attributes/builder.rb +1 -0
  16. data/lib/torque/postgresql/attributes/enum.rb +231 -0
  17. data/lib/torque/postgresql/attributes/lazy.rb +33 -0
  18. data/lib/torque/postgresql/attributes/type_map.rb +46 -0
  19. data/lib/torque/postgresql/attributes.rb +32 -0
  20. data/lib/torque/postgresql/auxiliary_statement.rb +192 -0
  21. data/lib/torque/postgresql/base.rb +28 -0
  22. data/lib/torque/postgresql/collector.rb +31 -0
  23. data/lib/torque/postgresql/config.rb +50 -0
  24. data/lib/torque/postgresql/migration/command_recorder.rb +31 -0
  25. data/lib/torque/postgresql/migration.rb +1 -0
  26. data/lib/torque/postgresql/relation/auxiliary_statement.rb +65 -0
  27. data/lib/torque/postgresql/relation/distinct_on.rb +43 -0
  28. data/lib/torque/postgresql/relation.rb +62 -0
  29. data/lib/torque/postgresql/schema_dumper.rb +37 -0
  30. data/lib/torque/postgresql/version.rb +5 -0
  31. data/lib/torque/postgresql.rb +18 -0
  32. data/lib/torque-postgresql.rb +1 -0
  33. metadata +236 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d27d24270072d57ca693a53b5fcd29ac72b509e0
4
+ data.tar.gz: cc63789993cf2817a2857d6cf5c6b8d31c38dabb
5
+ SHA512:
6
+ metadata.gz: 8b6bd7dc8af51496eecd1c7c7a33f4d3ce8e885e7918668068b8ecf8e7ac9961c1ee4cfe342ecf46c31ccead863b82fe4c3296e523cffd28a5d4ce4c7499be75
7
+ data.tar.gz: 41141267915f0e8ad1d5b6052cab7cc45c9e2bb37ff4d02fc4e819a4c8cf199ba33ea924884c8732bfd691c354908e330f8390e8836906806bc7e685ce2d7f84
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Carlos Silva
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Torque::Postgresql'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ desc 'Prints a schema dump of the test database'
18
+ task :dump do |t|
19
+ lib = File.expand_path('../lib', __FILE__)
20
+ spec = File.expand_path('../spec', __FILE__)
21
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
22
+ $LOAD_PATH.unshift(spec) unless $LOAD_PATH.include?(spec)
23
+
24
+ require 'byebug'
25
+ require 'spec_helper'
26
+ ActiveRecord::SchemaDumper.dump
27
+ end
28
+
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec)
31
+ task default: :spec
@@ -0,0 +1,103 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module DatabaseStatements
5
+
6
+ EXTENDED_DATABASE_TYPES = %i(enum interval)
7
+
8
+ # Check if a given type is valid.
9
+ def valid_type?(type)
10
+ super || extended_types.include?(type)
11
+ end
12
+
13
+ # Get the list of extended types
14
+ def extended_types
15
+ EXTENDED_DATABASE_TYPES
16
+ end
17
+
18
+ # Returns true if type exists.
19
+ def type_exists?(name)
20
+ user_defined_types.key? name.to_s
21
+ end
22
+ alias data_type_exists? type_exists?
23
+
24
+ # Configure the interval format
25
+ def configure_connection
26
+ super
27
+ execute("SET SESSION IntervalStyle TO 'iso_8601'", 'SCHEMA')
28
+ end
29
+
30
+ # Change some of the types being mapped
31
+ def initialize_type_map(m)
32
+ super
33
+ m.register_type 'interval', OID::Interval.new
34
+ end
35
+
36
+ # Add the composite types to be loaded too.
37
+ def load_additional_types(type_map, oids = nil)
38
+ super
39
+
40
+ filter = "AND a.typelem::integer IN (%s)" % oids.join(", ") if oids
41
+
42
+ query = <<-SQL
43
+ SELECT a.typelem AS oid, t.typname, t.typelem,
44
+ t.typdelim, t.typbasetype, t.typtype
45
+ FROM pg_type t
46
+ INNER JOIN pg_type a ON (a.oid = t.typarray)
47
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
48
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
49
+ AND t.typtype IN ( 'c','e' )
50
+ #{filter}
51
+ AND NOT EXISTS(
52
+ SELECT 1 FROM pg_catalog.pg_type el
53
+ WHERE el.oid = t.typelem AND el.typarray = t.oid
54
+ )
55
+ AND (t.typrelid = 0 OR (
56
+ SELECT c.relkind = 'c' FROM pg_catalog.pg_class c
57
+ WHERE c.oid = t.typrelid
58
+ ))
59
+ SQL
60
+
61
+ execute_and_clear(query, 'SCHEMA', []) do |records|
62
+ records.each do |row|
63
+ typtype = row['typtype']
64
+ type = begin
65
+ case
66
+ when typtype == 'e'.freeze then OID::Enum.create(row)
67
+ end
68
+ end
69
+
70
+ type_map.register_type row['oid'].to_i, type
71
+ end
72
+ end
73
+ end
74
+
75
+ # Gets a list of user defined types.
76
+ # You can even choose the +typcategory+ filter
77
+ def user_defined_types(category = nil)
78
+ category_condition = "AND typtype = '#{category}'" unless category.nil?
79
+ select_all(<<-SQL).rows.to_h
80
+ SELECT t.typname AS name,
81
+ CASE t.typtype
82
+ WHEN 'e' THEN 'enum'
83
+ END AS type
84
+ FROM pg_type t
85
+ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
86
+ WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
87
+ #{category_condition}
88
+ AND NOT EXISTS(
89
+ SELECT 1 FROM pg_catalog.pg_type el
90
+ WHERE el.oid = t.typelem AND el.typarray = t.oid
91
+ )
92
+ AND (t.typrelid = 0 OR (
93
+ SELECT c.relkind = 'c' FROM pg_catalog.pg_class c
94
+ WHERE c.oid = t.typrelid
95
+ ))
96
+ ORDER BY t.typtype DESC
97
+ SQL
98
+ end
99
+
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,19 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module OID
5
+ module Array
6
+
7
+ def initialize(subtype, delimiter = ',')
8
+ super
9
+ @pg_encoder = Coder
10
+ @pg_decoder = Coder
11
+ end
12
+
13
+ end
14
+
15
+ ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.prepend Array
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module OID
5
+ class Enum < ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Enum
6
+
7
+ attr_reader :name, :klass
8
+
9
+ def self.create(row)
10
+ new(row['typname'])
11
+ end
12
+
13
+ def initialize(name)
14
+ @name = name
15
+ @klass = Attributes::Enum.lookup(name)
16
+ end
17
+
18
+ def hash
19
+ [self.class, name].hash
20
+ end
21
+
22
+ def serialize(value)
23
+ return if value.blank?
24
+ value = cast_value(value)
25
+ value.to_s unless value.nil?
26
+ end
27
+
28
+ def assert_valid_value(value)
29
+ cast_value(value)
30
+ end
31
+
32
+ private
33
+
34
+ def cast_value(value)
35
+ return if value.blank?
36
+ return value if value.is_a?(@klass)
37
+ @klass.new(value)
38
+ rescue Attributes::Enum::EnumError
39
+ nil
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,94 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module OID
5
+ class Interval < ActiveModel::Type::Value
6
+
7
+ CAST_PARTS = [:years, :months, :days, :hours, :minutes, :seconds]
8
+
9
+ def type
10
+ :interval
11
+ end
12
+
13
+ # Accepts database-style string, numeric as seconds, array of parts
14
+ # padded to left, or a hash
15
+ #
16
+ # Examples:
17
+ # [12, 0, 0]
18
+ # produces: 12 hours, 0 minutes, and 0 seconds
19
+ #
20
+ # [nil, nil, 3, 0, 0, 0]
21
+ # produces: 3 days, 0 hours, 0 minutes, and 0 seconds
22
+ #
23
+ # {minutes: 12, seconds: 0}
24
+ # produces: 12 minutes, and 0 seconds
25
+ def cast(value)
26
+ return if value.blank?
27
+ case value
28
+ when ::String then deserialize(value)
29
+ when ::ActiveSupport::Duration then value
30
+ when ::Numeric
31
+ parts = CAST_PARTS.map do |part|
32
+ rest, value = value.divmod(1.send(part))
33
+ rest == 0 ? nil : [part, rest]
34
+ end
35
+ parts_to_duration(parts.compact)
36
+ when ::Array
37
+ value.compact!
38
+ parts = CAST_PARTS.drop(6 - value.size).zip(value).to_h
39
+ parts_to_duration(parts)
40
+ when ::Hash
41
+ parts_to_duration(value)
42
+ else
43
+ value
44
+ end
45
+ end
46
+
47
+ # Uses the ActiveSupport::Duration::ISO8601Parser
48
+ # See ActiveSupport::Duration#parse
49
+ # The value must be Integer when no precision is given
50
+ def deserialize(value)
51
+ return if value.blank?
52
+ parts = ActiveSupport::Duration::ISO8601Parser.new(value).parse!
53
+ parts_to_duration(parts)
54
+ end
55
+
56
+ # Uses the ActiveSupport::Duration::ISO8601Serializer
57
+ # See ActiveSupport::Duration#iso8601
58
+ def serialize(value)
59
+ return if value.blank?
60
+ value = cast(value) unless value.is_a?(ActiveSupport::Duration)
61
+ value.iso8601(precision: @scale)
62
+ end
63
+
64
+ # Always use the numeric value for schema dumper
65
+ def type_cast_for_schema(value)
66
+ cast(value).value.inspect
67
+ end
68
+
69
+ # Check if the user input has the correct format
70
+ def assert_valid_value(value)
71
+ # TODO: Implement!
72
+ end
73
+
74
+ # Transform a list of parts into a duration object
75
+ def parts_to_duration(parts)
76
+ parts = parts.to_h.with_indifferent_access.slice(*CAST_PARTS)
77
+ return 0.seconds if parts.blank?
78
+
79
+ seconds = 0
80
+ parts = parts.map do |part, num|
81
+ num = num.to_i unless num.is_a?(Numeric)
82
+ if num > 0
83
+ seconds += num.send(part).value
84
+ [part, num]
85
+ end
86
+ end
87
+ ActiveSupport::Duration.new(seconds, parts.compact)
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'oid/array'
2
+ require_relative 'oid/enum'
3
+ require_relative 'oid/interval'
4
+
5
+ module Torque
6
+ module PostgreSQL
7
+ module Adapter
8
+ module OID
9
+ end
10
+
11
+ ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
12
+ ActiveRecord::Type.register(:interval, OID::Interval, adapter: :postgresql)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module Quoting
5
+
6
+ Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name
7
+
8
+ # Quotes type names for use in SQL queries.
9
+ def quote_type_name(string, schema = nil)
10
+ name_schema, table = string.to_s.scan(/[^".\s]+|"[^"]*"/)
11
+ if table.nil?
12
+ table = name_schema
13
+ name_schema = nil
14
+ end
15
+
16
+ schema = schema || name_schema || 'public'
17
+ Name.new(schema, table).quoted
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module ColumnMethods
5
+
6
+ def interval(*args, **options)
7
+ args.each { |name| column(name, :interval, options) }
8
+ end
9
+
10
+ def enum(*args, **options)
11
+ args.each do |name|
12
+ type = options.fetch(:subtype, name)
13
+ column(name, type, options)
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ module ColumnDefinition
20
+ attr_accessor :subtype
21
+ end
22
+
23
+ ActiveRecord::ConnectionAdapters::PostgreSQL::Table.include ColumnMethods
24
+ ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition.include ColumnMethods
25
+ ActiveRecord::ConnectionAdapters::PostgreSQL::ColumnDefinition.include ColumnDefinition
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module ColumnDumper
5
+
6
+ # Adds +:subtype+ as a valid migration key
7
+ def migration_keys
8
+ super + [:subtype]
9
+ end
10
+
11
+ # Adds +:subtype+ option to the default set
12
+ def prepare_column_options(column)
13
+ spec = super
14
+
15
+ if subtype = schema_subtype(column)
16
+ spec[:subtype] = subtype
17
+ end
18
+
19
+ spec
20
+ end
21
+
22
+ private
23
+
24
+ def schema_subtype(column)
25
+ column.sql_type.to_sym.inspect if [:enum].include? column.type
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,89 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Adapter
4
+ module SchemaStatements
5
+
6
+ # Drops a type.
7
+ def drop_type(name, options = {})
8
+ force = options.fetch(:force, '').upcase
9
+ check = 'IF EXISTS' if options.fetch(:check, true)
10
+ execute <<-SQL
11
+ DROP TYPE #{check}
12
+ #{quote_type_name(name, options[:schema])} #{force}
13
+ SQL
14
+ end
15
+
16
+ # Renames a type.
17
+ def rename_type(type_name, new_name)
18
+ execute <<-SQL
19
+ ALTER TYPE #{quote_type_name(type_name)}
20
+ RENAME TO #{Quoting::Name.new(nil, new_name.to_s).quoted}
21
+ SQL
22
+ end
23
+
24
+ # Creates a new PostgreSQL enumerator type
25
+ #
26
+ # Example:
27
+ # create_enum 'status', ['foo', 'bar']
28
+ # create_enum 'status', ['foo', 'bar'], prefix: true
29
+ # create_enum 'status', ['foo', 'bar'], suffix: 'test'
30
+ # create_enum 'status', ['foo', 'bar'], force: true
31
+ def create_enum(name, values, options = {})
32
+ drop_type(name, options) if options[:force]
33
+ execute <<-SQL
34
+ CREATE TYPE #{quote_type_name(name, options[:schema])} AS ENUM
35
+ (#{quote_enum_values(name, values, options).join(', ')})
36
+ SQL
37
+ end
38
+
39
+ # Changes the enumerator by adding new values
40
+ #
41
+ # Example:
42
+ # add_enum_values 'status', ['baz']
43
+ # add_enum_values 'status', ['baz'], before: 'bar'
44
+ # add_enum_values 'status', ['baz'], after: 'foo'
45
+ # add_enum_values 'status', ['baz'], prepend: true
46
+ def add_enum_values(name, values, options = {})
47
+ before = options.fetch(:before, false)
48
+ after = options.fetch(:after, false)
49
+
50
+ before = enum_values(name).first if options.key? :prepend
51
+ before = quote(before) unless before == false
52
+ after = quote(after) unless after == false
53
+
54
+ quote_enum_values(name, values, options).each do |value|
55
+ reference = "BEFORE #{before}" unless before == false
56
+ reference = "AFTER #{after}" unless after == false
57
+ execute <<-SQL
58
+ ALTER TYPE #{quote_type_name(name, options[:schema])}
59
+ ADD VALUE #{value} #{reference}
60
+ SQL
61
+
62
+ before = false
63
+ after = value
64
+ end
65
+ end
66
+
67
+ # Returns all values that an enum type can have.
68
+ def enum_values(name)
69
+ select_values("SELECT unnest(enum_range(NULL::#{name}))")
70
+ end
71
+
72
+ private
73
+
74
+ def quote_enum_values(name, values, options)
75
+ prefix = options[:prefix]
76
+ prefix = name if prefix === true
77
+
78
+ suffix = options[:suffix]
79
+ suffix = name if suffix === true
80
+
81
+ values.map! do |value|
82
+ quote([prefix, value, suffix].compact.join('_'))
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end