dumbo 0.0.1

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +61 -0
  6. data/Rakefile +9 -0
  7. data/bin/dumbo +58 -0
  8. data/config/boot.rb +15 -0
  9. data/config/database.yml +31 -0
  10. data/dumbo.gemspec +31 -0
  11. data/lib/dumbo.rb +21 -0
  12. data/lib/dumbo/aggregate.rb +57 -0
  13. data/lib/dumbo/base_type.rb +71 -0
  14. data/lib/dumbo/cast.rb +49 -0
  15. data/lib/dumbo/composite_type.rb +31 -0
  16. data/lib/dumbo/db_task.rb +57 -0
  17. data/lib/dumbo/dependency_resolver.rb +105 -0
  18. data/lib/dumbo/enum_type.rb +28 -0
  19. data/lib/dumbo/extension.rb +73 -0
  20. data/lib/dumbo/extension_migrator.rb +66 -0
  21. data/lib/dumbo/extension_version.rb +25 -0
  22. data/lib/dumbo/function.rb +101 -0
  23. data/lib/dumbo/operator.rb +74 -0
  24. data/lib/dumbo/pg_object.rb +80 -0
  25. data/lib/dumbo/rake_task.rb +121 -0
  26. data/lib/dumbo/range_type.rb +43 -0
  27. data/lib/dumbo/type.rb +31 -0
  28. data/lib/dumbo/version.rb +3 -0
  29. data/lib/tasks/db.rake +52 -0
  30. data/lib/tasks/dumbo.rake +23 -0
  31. data/spec/Makefile +6 -0
  32. data/spec/aggregate_spec.rb +41 -0
  33. data/spec/cast_spec.rb +20 -0
  34. data/spec/dumbo_sample--0.0.1.sql +5 -0
  35. data/spec/dumbo_sample--0.0.2.sql +5 -0
  36. data/spec/dumbo_sample.control +5 -0
  37. data/spec/extension_migrator_spec.rb +40 -0
  38. data/spec/extension_spec.rb +19 -0
  39. data/spec/operator_spec.rb +42 -0
  40. data/spec/spec_helper.rb +28 -0
  41. data/spec/support/sql_helper.rb +23 -0
  42. data/spec/type_spec.rb +95 -0
  43. data/template/Gemfile +3 -0
  44. data/template/Makefile.erb +6 -0
  45. data/template/Rakefile +15 -0
  46. data/template/config/database.yml.erb +31 -0
  47. data/template/spec/sample_spec.rb.erb +13 -0
  48. data/template/sql/sample.sql +5 -0
  49. metadata +230 -0
@@ -0,0 +1,74 @@
1
+ module Dumbo
2
+ class Operator < PgObject
3
+ attr_accessor :name,
4
+ :kind,
5
+ :hashes,
6
+ :merges,
7
+ :leftarg,
8
+ :rightarg,
9
+ :result_type,
10
+ :commutator,
11
+ :negator,
12
+ :function_name,
13
+ :join,
14
+ :restrict
15
+
16
+ identfied_by :name, :leftarg, :rightarg
17
+
18
+ def load_attributes
19
+
20
+ result = execute <<-SQL
21
+ SELECT
22
+ op.oprname AS name,
23
+ op.oprkind AS kind,
24
+ op.oprcanhash AS hashes,
25
+ op.oprcanmerge AS merges,
26
+ lt.typname AS leftarg,
27
+ rt.typname AS rightarg,
28
+ et.typname AS result_type,
29
+ co.oprname AS commutator,
30
+ ne.oprname AS negator,
31
+ op.oprcode AS function_name,
32
+ op.oprjoin AS join,
33
+ op.oprrest AS restrict,
34
+ description
35
+ FROM pg_operator op
36
+ LEFT OUTER JOIN pg_type lt ON lt.oid=op.oprleft
37
+ LEFT OUTER JOIN pg_type rt ON rt.oid=op.oprright
38
+ JOIN pg_type et on et.oid=op.oprresult
39
+ LEFT OUTER JOIN pg_operator co ON co.oid=op.oprcom
40
+ LEFT OUTER JOIN pg_operator ne ON ne.oid=op.oprnegate
41
+ LEFT OUTER JOIN pg_description des ON des.objoid=op.oid
42
+ WHERE op.oid = #{oid}
43
+ SQL
44
+
45
+ result.first.each do |k,v|
46
+ send("#{k}=",v) rescue nil
47
+ end
48
+
49
+ result.first
50
+
51
+ end
52
+
53
+ def to_sql
54
+ attrs = [:leftarg, :rightarg, :commutator, :negator, :restrict, :join].inject([]) do |mem, attr|
55
+ mem << "#{attr.to_s.upcase} = #{public_send(attr)}" if public_send(attr)
56
+ mem
57
+ end
58
+ atttr_str = attrs.join(",\n ")
59
+ attrs << ",\n HASHES" if hashes
60
+ attrs << ",\n MERGES" if merges
61
+
62
+ <<-SQL.gsub(/^ {6}/, '')
63
+ CREATE OPERATOR #{name} (
64
+ PROCEDURE = #{function_name},
65
+ #{atttr_str}
66
+ );
67
+ SQL
68
+ end
69
+
70
+ def drop
71
+ "DROP OPERATOR #{name};"
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,80 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+
3
+ module Dumbo
4
+ class PgObject
5
+ attr_reader :oid
6
+ class_attribute :identifier
7
+
8
+ class << self
9
+ def identfied_by(*args)
10
+ self.identifier = args
11
+ end
12
+ end
13
+
14
+ def initialize(oid)
15
+ @oid = oid
16
+ load_attributes
17
+ end
18
+
19
+ def identify
20
+ identifier.map{|a| public_send a}
21
+ end
22
+
23
+ def get(type=nil)
24
+ case type
25
+ when 'function', 'pg_proc'
26
+ Function.new(oid).get
27
+ when 'cast', 'pg_cast'
28
+ Cast.new(oid).get
29
+ when 'operator', 'pg_operator'
30
+ Operator.new(oid).get
31
+ when 'type', 'pg_type'
32
+ Type.new(oid).get
33
+ else
34
+ self.load_attributes
35
+ self
36
+ end
37
+ end
38
+
39
+ def load_attributes
40
+
41
+ end
42
+
43
+ def upgrade(other)
44
+ return self.to_sql if other.nil?
45
+
46
+ if other.identify != self.identify
47
+ raise "Not the Same Objects!"
48
+ end
49
+
50
+ if other.to_sql != self.to_sql
51
+ <<-SQL.gsub(/^ {8}/, '')
52
+ #{self.drop}
53
+ #{self.to_sql}
54
+ SQL
55
+ end
56
+
57
+
58
+ end
59
+
60
+ def downgrade(other)
61
+ return self.drop if other.nil?
62
+
63
+ if other.identify != self.identify
64
+ raise "Not the Same Objects!"
65
+ end
66
+
67
+ if other.to_sql != self.to_sql
68
+ <<-SQL.gsub(/^ {8}/, '')
69
+ #{self.drop}
70
+ #{other.to_sql}
71
+ SQL
72
+ end
73
+ end
74
+
75
+ def execute(sql)
76
+ ActiveRecord::Base.connection.execute(sql)
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,121 @@
1
+ require "rake"
2
+ require 'rake/tasklib'
3
+ require "erubis"
4
+ require "pathname"
5
+ require 'yaml'
6
+ require 'logger'
7
+ require 'active_record'
8
+ require "dumbo/extension"
9
+ require "dumbo/dependency_resolver"
10
+
11
+ module Dumbo
12
+ class RakeTask < ::Rake::TaskLib
13
+ attr_accessor :name
14
+
15
+ def initialize(name = 'dumbo')
16
+
17
+ @name = name
18
+
19
+ namespace name do
20
+ desc 'creates and installs extension'
21
+ task :all => ["#{extension}--#{version}.sql", :install]
22
+
23
+ desc 'installs the extension'
24
+ task :install do
25
+ system('make clean && make && make install')
26
+ end
27
+
28
+ desc 'concatenates files'
29
+ file "#{extension}--#{version}.sql" => file_list do |t|
30
+ sql = t.prerequisites.map do |file|
31
+ ["--source file #{file}"] + get_sql(file) + [" "]
32
+ end.flatten
33
+ concatenate sql, t.name
34
+ end
35
+
36
+ desc 'creates migration files for the last two versions'
37
+ task :migrations do
38
+ old_version, new_version = Dumbo::Extension.new.available_versions.last(2).map(&:to_s)
39
+ if new_version
40
+ Dumbo::ExtensionMigrator.new(Dumbo::Extension.new.name, old_version, new_version).create
41
+ end
42
+ end
43
+
44
+ desc 'release a new version'
45
+ task :new_version, :level do |t, args|
46
+ args.with_defaults(:level => 'patch')
47
+ v = version_bump args[:level]
48
+ set_version v
49
+
50
+ Rake::Task["#{name}:all"].invoke
51
+ end
52
+ end
53
+ end
54
+
55
+ def set_version(new_version)
56
+ content = File.read("#{extension}.control")
57
+ new_content = content.gsub(version, new_version)
58
+ File.open("#{extension}.control", "w") {|file| file.puts new_content}
59
+ end
60
+
61
+ def version_bump(level='patch')
62
+ levels = {'patch' => 2, 'minor' => 1, 'major' => 0}
63
+ parts = version.split('.').map(&:to_i)
64
+ l = levels[level]
65
+ parts[l] += 1
66
+ (l+1..2).each {|l| parts[l]=0}
67
+
68
+ parts.join('.')
69
+ end
70
+
71
+ def version
72
+ Dumbo::Extension.new.version
73
+ end
74
+
75
+ def extension
76
+ Dumbo::Extension.new.name
77
+ end
78
+
79
+ def available_versions
80
+ Dumbo::Extension.new.name.available_versions
81
+ end
82
+
83
+ # source sql file list
84
+ def file_list
85
+ Dumbo::DependencyResolver.new(Dir.glob("sql/**/*.{sql,erb}")).resolve
86
+ end
87
+
88
+ def concatenate(lines, target)
89
+ File.open(target,'w') do |f|
90
+ lines.each do |line|
91
+ f.puts line unless line =~ Dumbo::DependencyResolver.depends_pattern
92
+ end
93
+ end
94
+ end
95
+
96
+ def get_sql(file)
97
+ ext = Pathname.new(file).extname
98
+ if ext == '.erb'
99
+ convert_template(file)
100
+ else
101
+ File.readlines(file)
102
+ end
103
+ end
104
+
105
+ def convert_template(file)
106
+ eruby = Erubis::Eruby.new(File.read(file))
107
+ bindigs = get_bindings(file)
108
+ eruby.result(bindigs).split("\n")
109
+ end
110
+
111
+ def get_bindings(file)
112
+ base = Pathname.new(file).sub_ext('.yml').basename
113
+ yaml = Pathname.new('config').join(base)
114
+ if yaml.exist?
115
+ YAML.load_file(yaml)
116
+ else
117
+ {}
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,43 @@
1
+ module Dumbo
2
+ class RangeType < Type
3
+ attr_accessor :subtype, :subtype_opclass, :collation,
4
+ :canonical,:subtype_diff
5
+
6
+ def load_attributes
7
+ super
8
+ result = execute <<-SQL
9
+ SELECT
10
+ st.typname AS subtype,
11
+ opc.opcname AS subtype_opclass,
12
+ col.collname AS collation,
13
+ rngcanonical AS canonical,
14
+ rngsubdiff AS subtype_diff
15
+ FROM pg_range
16
+ LEFT JOIN pg_type st ON st.oid=rngsubtype
17
+ LEFT JOIN pg_collation col ON col.oid=rngcollation
18
+ LEFT JOIN pg_opclass opc ON opc.oid=rngsubopc
19
+ WHERE rngtypid=#{oid}
20
+ SQL
21
+
22
+ result.first.each do |k,v|
23
+ send("#{k}=",v) rescue nil
24
+ end
25
+ result.first
26
+ end
27
+
28
+
29
+
30
+
31
+ def to_sql
32
+ attr_str = [:subtype, :subtype_opclass, :collation, :canonical,:subtype_diff].map do |a|
33
+ [a, public_send(a)]
34
+ end.select{|k,v| v && v != '-' }.map{|k,v| "#{k.upcase}=#{v}"}.join(",\n ")
35
+
36
+ <<-SQL.gsub(/^ {6}/, '')
37
+ CREATE TYPE #{name} AS RANGE (
38
+ #{attr_str}
39
+ );
40
+ SQL
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ module Dumbo
2
+ class Type < PgObject
3
+ attr_accessor :name, :type, :typrelid
4
+ identfied_by :name
5
+
6
+ def load_attributes
7
+ result = execute("SELECT typname, typtype, typrelid FROM pg_type WHERE oid = #{oid}").first
8
+ @name = result['typname']
9
+ @type = result['typtype']
10
+ @typrelid = result['typrelid']
11
+ end
12
+
13
+ def get
14
+ case type
15
+ when 'c'
16
+ CompositeType.new(oid)
17
+ when 'b'
18
+ BaseType.new(oid)
19
+ when 'r'
20
+ RangeType.new(oid)
21
+ when 'e'
22
+ EnumType.new(oid)
23
+ end
24
+ end
25
+
26
+ def drop
27
+ "DROP TYPE #{name}"
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Dumbo
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,52 @@
1
+ require 'yaml'
2
+ require 'logger'
3
+ require 'active_record'
4
+
5
+ task environment: ['db:configure_connection' ]
6
+
7
+ task :test_env do
8
+ ENV['DUMBO_ENV'] = 'test'
9
+ end
10
+
11
+ namespace :db do
12
+ def create_database(config)
13
+ ActiveRecord::Base.establish_connection config.merge('database' => nil)
14
+ ActiveRecord::Base.connection.create_database config['database'], config
15
+ ActiveRecord::Base.establish_connection config
16
+ end
17
+
18
+ task :configuration do
19
+ @config = YAML.load_file('config/database.yml')[ENV['DUMBO_ENV']]
20
+ end
21
+
22
+ task configure_connection: :configuration do
23
+ ActiveRecord::Base.establish_connection @config
24
+ ActiveRecord::Base.logger = Logger.new STDOUT if @config['logger']
25
+ end
26
+
27
+ desc 'Create the database from config/database.yml for the current ENV'
28
+ task create: :environment do
29
+ create_database @config
30
+ end
31
+
32
+ desc 'Drops the database for the current ENV'
33
+ task drop: :environment do
34
+ ActiveRecord::Base.establish_connection @config.merge('database' => nil)
35
+ ActiveRecord::Base.connection.drop_database @config['database']
36
+ end
37
+
38
+ namespace :test do
39
+ task :environment do
40
+ ENV['DUMBO_ENV'] ||= 'test'
41
+ ActiveRecord::Schema.verbose = false
42
+ end
43
+
44
+ task load_structure: :environment do
45
+ filename = ENV['DB_STRUCTURE'] || File.join("db", "structure.sql")
46
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(@config, filename)
47
+ end
48
+
49
+ desc "Re-create and prepare test database"
50
+ task prepare: [:environment, :drop, :create]
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ require 'erubis'
2
+ require 'pathname'
3
+
4
+ task :default => ['dumbo:all', :spec]
5
+
6
+ namespace :dumbo do
7
+ include Dumbo::RakeHelper
8
+
9
+ task :all => ["#{extension}--#{version}.sql", :install]
10
+
11
+ desc 'installs the extension'
12
+ task :install do
13
+ system('make clean && make && make install')
14
+ end
15
+
16
+ desc 'concatenates files'
17
+ file "#{extension}--#{version}.sql" => file_list do |t|
18
+ sql = t.prerequisites.map do |file|
19
+ ["--source file #{file}"] + get_sql(file) + [" "]
20
+ end.flatten
21
+ concatenate sql, t.name
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ #http://blog.pgxn.org/post/4783001135/extension-makefiles pg makefiles
2
+ EXTENSION = dumbo_sample
3
+ PG_CONFIG ?= pg_config
4
+ DATA = $(wildcard *--*.sql)
5
+ PGXS := $(shell $(PG_CONFIG) --pgxs)
6
+ include $(PGXS)
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+ describe Dumbo::Aggregate do
3
+ let(:avg) do
4
+ oid = sql("SELECT p.oid
5
+ FROM pg_proc p
6
+ JOIN pg_aggregate ag ON p.oid = ag.aggfnoid
7
+ WHERE proname='avg' AND pg_get_function_arguments(p.oid) = 'integer'",'oid').first
8
+ Dumbo::Aggregate.new(oid)
9
+ end
10
+
11
+ let(:min) do
12
+ oid = sql("SELECT p.oid
13
+ FROM pg_proc p
14
+ JOIN pg_aggregate ag ON p.oid = ag.aggfnoid
15
+ WHERE proname='min' AND pg_get_function_arguments(p.oid) = 'integer'",'oid').first
16
+ Dumbo::Aggregate.new(oid)
17
+ end
18
+
19
+
20
+ it "avg should have a sql representation" do
21
+ avg.to_sql.should eq <<-SQL.gsub(/^ {6}/, '')
22
+ CREATE AGGREGATE avg(integer) (
23
+ SFUNC = int4_avg_accum,
24
+ STYPE = int8[],
25
+ FINALFUNC = int8_avg,
26
+ INITCOND = '{0,0}'
27
+ );
28
+ SQL
29
+ end
30
+
31
+ it "min should have a sql representation" do
32
+ min.to_sql.should eq <<-SQL.gsub(/^ {6}/, '')
33
+ CREATE AGGREGATE min(integer) (
34
+ SFUNC = int4smaller,
35
+ STYPE = int4,
36
+ SORTOP = <
37
+ );
38
+ SQL
39
+ end
40
+
41
+ end