mystic 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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/bin/mystic +43 -0
- data/lib/mystic.rb +183 -0
- data/lib/mystic/adapter.rb +145 -0
- data/lib/mystic/adapters/abstract.rb +90 -0
- data/lib/mystic/adapters/mysql.rb +51 -0
- data/lib/mystic/adapters/postgres.rb +95 -0
- data/lib/mystic/constants.rb +11 -0
- data/lib/mystic/extensions.rb +99 -0
- data/lib/mystic/migration.rb +108 -0
- data/lib/mystic/model.rb +130 -0
- data/lib/mystic/sql.rb +244 -0
- metadata +99 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: e7f8ba8f872bed5ad3946fb3eca8373a5262c820
         | 
| 4 | 
            +
              data.tar.gz: 874d9a10179f9c9e15a6fffb7451ff03ad70788a
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: c10435a23302bdf3e5aec546b164b4dc73a021b9d12221e2e1387e26086a2db21f603896aa3ef0b49daaa464438d1cec143861ecac0cb7421e057c9489cfd98a
         | 
| 7 | 
            +
              data.tar.gz: 5333e387710b4b0d19b81664359becf2a79f101e9d655820d256388a4488543b3587219514e4a2d2c212d931cb7806ee522546e75f974b2f838cdd8eb19af919
         | 
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            The MIT License (MIT)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) <year> <copyright holders>
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 | 
            +
            of this software and associated documentation files (the "Software"), to deal
         | 
| 7 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 8 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 9 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 10 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            The above copyright notice and this permission notice shall be included in
         | 
| 13 | 
            +
            all copies or substantial portions of the Software.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 16 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 17 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 18 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 19 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 20 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
         | 
| 21 | 
            +
            THE SOFTWARE.
         | 
    
        data/bin/mystic
    ADDED
    
    | @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "mystic"
         | 
| 4 | 
            +
            require "irb"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # Examples:
         | 
| 7 | 
            +
            # mystic create migration InitialMigration
         | 
| 8 | 
            +
            # mystic migrate development
         | 
| 9 | 
            +
            # mystic rollback production
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ErrorOutput = Class.new StandardError
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            begin
         | 
| 14 | 
            +
            	case ARGV[0].to_sym
         | 
| 15 | 
            +
            	when :create
         | 
| 16 | 
            +
            	  case ARGV[1].to_sym
         | 
| 17 | 
            +
            	  when :migration
         | 
| 18 | 
            +
            			raise ErrorOutput, "No migration name provided" if ARGV[2].nil?
         | 
| 19 | 
            +
            			Mystic.create_migration ARGV[2].dup
         | 
| 20 | 
            +
            	  end
         | 
| 21 | 
            +
            	else
         | 
| 22 | 
            +
            		begin
         | 
| 23 | 
            +
            			Mystic.connect ARGV[1]
         | 
| 24 | 
            +
            		  case ARGV[0].to_sym
         | 
| 25 | 
            +
            		  when :migrate
         | 
| 26 | 
            +
            		  	Mystic.migrate
         | 
| 27 | 
            +
            		  when :rollback
         | 
| 28 | 
            +
            		  	Mystic.rollback
         | 
| 29 | 
            +
            			when :console
         | 
| 30 | 
            +
            				puts "Starting Mystic console: #{Mystic.env}"
         | 
| 31 | 
            +
            				IRB.setup nil
         | 
| 32 | 
            +
            				IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
         | 
| 33 | 
            +
            				require "irb/ext/multi-irb"
         | 
| 34 | 
            +
            				IRB.irb nil, IRB::WorkSpace.new
         | 
| 35 | 
            +
            			end
         | 
| 36 | 
            +
            		 	Mystic.disconnect
         | 
| 37 | 
            +
            		rescue Mystic::EnvironmentError => e
         | 
| 38 | 
            +
            			puts e.message
         | 
| 39 | 
            +
            		end
         | 
| 40 | 
            +
            	end
         | 
| 41 | 
            +
            rescue ErrorOutput => e
         | 
| 42 | 
            +
            	puts e.message
         | 
| 43 | 
            +
            end
         | 
    
        data/lib/mystic.rb
    ADDED
    
    | @@ -0,0 +1,183 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "yaml"
         | 
| 4 | 
            +
            require "pathname"
         | 
| 5 | 
            +
            require "mystic/extensions"
         | 
| 6 | 
            +
            require "mystic/constants"
         | 
| 7 | 
            +
            require "mystic/sql"
         | 
| 8 | 
            +
            require "mystic/adapter"
         | 
| 9 | 
            +
            require "mystic/migration"
         | 
| 10 | 
            +
            require "mystic/model"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module Mystic	
         | 
| 13 | 
            +
            	@@adapter = nil
         | 
| 14 | 
            +
            	
         | 
| 15 | 
            +
            	class << self
         | 
| 16 | 
            +
            		def adapter
         | 
| 17 | 
            +
            			@@adapter
         | 
| 18 | 
            +
            		end
         | 
| 19 | 
            +
            		
         | 
| 20 | 
            +
            		# Mystic.connect
         | 
| 21 | 
            +
            		#   Connects to a database. It's recommended you use it like ActiveRecord::Base.establish_connection
         | 
| 22 | 
            +
            		# Arguments:
         | 
| 23 | 
            +
            		#   env - The env from database.yml you wish to use
         | 
| 24 | 
            +
            		def connect(env="")
         | 
| 25 | 
            +
            			load_env
         | 
| 26 | 
            +
            			@@env = (env || ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development").to_s
         | 
| 27 | 
            +
            			path = root.join("config","database.yml").to_s
         | 
| 28 | 
            +
            			db_yml = YAML.load_file path
         | 
| 29 | 
            +
            		
         | 
| 30 | 
            +
            			raise EnvironmentError, "Environment '#{@@env}' doesn't exist." unless db_yml.member? @@env
         | 
| 31 | 
            +
            		
         | 
| 32 | 
            +
            			conf = db_yml[@@env].symbolize
         | 
| 33 | 
            +
            			conf[:dbname] = conf[:database]
         | 
| 34 | 
            +
            			
         | 
| 35 | 
            +
            			@@adapter = Adapter.create(
         | 
| 36 | 
            +
            				conf[:adapter],
         | 
| 37 | 
            +
            				:pool_size => conf[:pool_size].to_i,
         | 
| 38 | 
            +
            				:pool_timeout => conf[:timeout].to_i,
         | 
| 39 | 
            +
            				:pool_expires => conf[:expires].to_i
         | 
| 40 | 
            +
            			)
         | 
| 41 | 
            +
            		
         | 
| 42 | 
            +
            			@@adapter.connect conf
         | 
| 43 | 
            +
            			true
         | 
| 44 | 
            +
            		end
         | 
| 45 | 
            +
            	
         | 
| 46 | 
            +
            		alias_method :env=, :connect
         | 
| 47 | 
            +
            	
         | 
| 48 | 
            +
            		def env
         | 
| 49 | 
            +
            			@@env
         | 
| 50 | 
            +
            		end
         | 
| 51 | 
            +
            		
         | 
| 52 | 
            +
            		# Mystic.disconnect
         | 
| 53 | 
            +
            		#   Disconnects from the connected database. Use it like ActiveRecord::Base.connection.disconnect!
         | 
| 54 | 
            +
            		def disconnect
         | 
| 55 | 
            +
            			@@adapter.disconnect
         | 
| 56 | 
            +
            			@@adapter = nil
         | 
| 57 | 
            +
            			true
         | 
| 58 | 
            +
            		end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            		# Mystic.execute
         | 
| 61 | 
            +
            		#   Execute some sql. It will be densified (the densify gem) and sent to the DB
         | 
| 62 | 
            +
            		# Arguments:
         | 
| 63 | 
            +
            		#   sql - The SQL to execute
         | 
| 64 | 
            +
            		# Returns: Native Ruby objects representing the response from the DB (Usually an Array of Hashes)
         | 
| 65 | 
            +
            		def execute(sql="")
         | 
| 66 | 
            +
            			raise AdapterError, "Adapter is nil, so Mystic is not connected." if @@adapter.nil?
         | 
| 67 | 
            +
            			@@adapter.execute sql.sql_terminate.densify
         | 
| 68 | 
            +
            		end
         | 
| 69 | 
            +
              
         | 
| 70 | 
            +
            		# Mystic.sanitize
         | 
| 71 | 
            +
            		#   Escape a string so that it can be used safely as input. Mystic does not support statement preparation, so this is a must.
         | 
| 72 | 
            +
            		# Arguments:
         | 
| 73 | 
            +
            		#   str - The string to sanitize
         | 
| 74 | 
            +
            		# Returns: the sanitized string
         | 
| 75 | 
            +
            		def sanitize(str="")
         | 
| 76 | 
            +
            			raise AdapterError, "Adapter is nil, so Mystic is not connected." if @@adapter.nil?
         | 
| 77 | 
            +
            			@@adapter.sanitize str
         | 
| 78 | 
            +
            		end
         | 
| 79 | 
            +
            	
         | 
| 80 | 
            +
            		# Mystic.root
         | 
| 81 | 
            +
            		#   Get the app root
         | 
| 82 | 
            +
            		# Aguments:
         | 
| 83 | 
            +
            		#   To be ignored
         | 
| 84 | 
            +
            		# Returns:
         | 
| 85 | 
            +
            		#   A pathname to the application's root
         | 
| 86 | 
            +
            		def root(path=Pathname.new(Dir.pwd))
         | 
| 87 | 
            +
            			raise RootError, "Failed to find the application's root." if path == path.parent
         | 
| 88 | 
            +
            			mystic_path = path.join "config", "database.yml"
         | 
| 89 | 
            +
            			return path if mystic_path.file? # exist? is implicit with file?
         | 
| 90 | 
            +
            			root path.parent
         | 
| 91 | 
            +
            		end
         | 
| 92 | 
            +
            	
         | 
| 93 | 
            +
            		# TODO: Make this a migration
         | 
| 94 | 
            +
            		# TODO: Silence this
         | 
| 95 | 
            +
            		# Mystic.create_table
         | 
| 96 | 
            +
            		#   Create migration tracking table
         | 
| 97 | 
            +
            		def create_mig_table
         | 
| 98 | 
            +
            			execute "CREATE TABLE IF NOT EXISTS mystic_migrations (mig_number integer, filename text)"
         | 
| 99 | 
            +
            		end
         | 
| 100 | 
            +
            	
         | 
| 101 | 
            +
            		#
         | 
| 102 | 
            +
            		# Command line
         | 
| 103 | 
            +
            		#
         | 
| 104 | 
            +
            		
         | 
| 105 | 
            +
            		# Runs every yet-to-be-ran migration
         | 
| 106 | 
            +
            		def migrate
         | 
| 107 | 
            +
            			create_mig_table
         | 
| 108 | 
            +
            			migrated_filenames = execute("SELECT filename FROM mystic_migrations").map{ |r| r["filename"] }
         | 
| 109 | 
            +
            			mp = root.join("mystic","migrations").to_s
         | 
| 110 | 
            +
            		
         | 
| 111 | 
            +
            			Dir.entries(mp)
         | 
| 112 | 
            +
            				.reject{ |e| MIG_REGEX.match(e).nil? }
         | 
| 113 | 
            +
            				.reject{ |e| migrated_filenames.include? e }
         | 
| 114 | 
            +
            				.sort{ |a,b| MIG_REGEX.match(a)[:num].to_i <=> MIG_REGEX.match(b)[:num].to_i }
         | 
| 115 | 
            +
            				.each{ |fname|
         | 
| 116 | 
            +
            					load File.join mp,fname
         | 
| 117 | 
            +
            				
         | 
| 118 | 
            +
            					mig_num,mig_name = MIG_REGEX.match(fname).captures
         | 
| 119 | 
            +
            					Object.const_get(mig_name).new.migrate
         | 
| 120 | 
            +
            					execute "INSERT INTO mystic_migrations (mig_number, filename) VALUES (#{mig_num},'#{fname}')"
         | 
| 121 | 
            +
            				}
         | 
| 122 | 
            +
            		end
         | 
| 123 | 
            +
            	
         | 
| 124 | 
            +
            		# Rolls back a single migration
         | 
| 125 | 
            +
            		def rollback
         | 
| 126 | 
            +
            			create_mig_table
         | 
| 127 | 
            +
            			fname = execute("SELECT filename FROM mystic_migrations ORDER BY mig_number DESC LIMIT 1")[0]["filename"] rescue nil
         | 
| 128 | 
            +
            			return if fname.nil?
         | 
| 129 | 
            +
             | 
| 130 | 
            +
            			load root.join("mystic","migrations",fname).to_s
         | 
| 131 | 
            +
            		
         | 
| 132 | 
            +
            			mig_num,mig_name = MIG_REGEX.match(fname).captures
         | 
| 133 | 
            +
             | 
| 134 | 
            +
            			Object.const_get(mig_name).new.rollback
         | 
| 135 | 
            +
            		
         | 
| 136 | 
            +
            			execute "DELETE FROM mystic_migrations WHERE filename='#{fname}' and mig_number=#{mig_num}"
         | 
| 137 | 
            +
            		end
         | 
| 138 | 
            +
            	
         | 
| 139 | 
            +
            	  # Creates a blank migration in mystic/migrations
         | 
| 140 | 
            +
            		def create_migration(name="")
         | 
| 141 | 
            +
            			name.strip!
         | 
| 142 | 
            +
            			raise CLIError, "Migration name must not be empty." if name.empty?
         | 
| 143 | 
            +
            		
         | 
| 144 | 
            +
            			name[0] = name[0].capitalize
         | 
| 145 | 
            +
                
         | 
| 146 | 
            +
            			migs = root.join "mystic","migrations"
         | 
| 147 | 
            +
             | 
| 148 | 
            +
            			num = migs.entries.map{ |e| MIG_REGEX.match(e.to_s)[:num].to_i rescue 0 }.max.to_i+1
         | 
| 149 | 
            +
             | 
| 150 | 
            +
            			File.open(migs.join("#{num}_#{name}.rb").to_s, 'w') { |f| f.write(template name) }
         | 
| 151 | 
            +
            		end
         | 
| 152 | 
            +
            	
         | 
| 153 | 
            +
            		private
         | 
| 154 | 
            +
            		
         | 
| 155 | 
            +
            		# Loads the .env file
         | 
| 156 | 
            +
            		def load_env
         | 
| 157 | 
            +
            			root.join(".env").read
         | 
| 158 | 
            +
            											 .split("\n")
         | 
| 159 | 
            +
            											 .map { |l| l.strip.split "=", 2 }
         | 
| 160 | 
            +
            											 .each { |k,v| ENV[k] = v }
         | 
| 161 | 
            +
            		end
         | 
| 162 | 
            +
            			
         | 
| 163 | 
            +
            		# Retuns a blank migration's code in a String
         | 
| 164 | 
            +
            		def template(name=nil)
         | 
| 165 | 
            +
            			raise ArgumentError, "Migrations must have a name." if name.nil?
         | 
| 166 | 
            +
            			<<-mig_template
         | 
| 167 | 
            +
            #!/usr/bin/env ruby
         | 
| 168 | 
            +
             | 
| 169 | 
            +
            require "mystic"
         | 
| 170 | 
            +
             | 
| 171 | 
            +
            class #{name} < Mystic::Migration
         | 
| 172 | 
            +
              def up
         | 
| 173 | 
            +
            		
         | 
| 174 | 
            +
              end
         | 
| 175 | 
            +
              
         | 
| 176 | 
            +
              def down
         | 
| 177 | 
            +
            		
         | 
| 178 | 
            +
              end
         | 
| 179 | 
            +
            end
         | 
| 180 | 
            +
            			mig_template
         | 
| 181 | 
            +
            		end
         | 
| 182 | 
            +
            	end
         | 
| 183 | 
            +
            end
         | 
| @@ -0,0 +1,145 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "densify"
         | 
| 4 | 
            +
            require "access_stack"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Mystic
         | 
| 7 | 
            +
              class Adapter
         | 
| 8 | 
            +
                attr_accessor :pool_size,
         | 
| 9 | 
            +
            									:pool_timeout,
         | 
| 10 | 
            +
            									:pool_expires
         | 
| 11 | 
            +
              
         | 
| 12 | 
            +
                @@blocks = {}
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def self.create(name="",opts={})
         | 
| 15 | 
            +
            			name = name.to_s.downcase.strip
         | 
| 16 | 
            +
            			name = "postgres" if name =~ /^postg.*$/i # Includes PostGIS
         | 
| 17 | 
            +
            			name = "mysql" if name =~ /^mysql.*$/i
         | 
| 18 | 
            +
            		
         | 
| 19 | 
            +
            			require "mystic/adapters/" + name
         | 
| 20 | 
            +
            		
         | 
| 21 | 
            +
            			Object.const_get("Mystic::#{name.capitalize}Adapter").new opts
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
                
         | 
| 24 | 
            +
                def initialize(opts={})
         | 
| 25 | 
            +
                	opts.symbolize.each do |k,v|
         | 
| 26 | 
            +
                		k = ('@' + k.to_s).to_sym
         | 
| 27 | 
            +
                		instance_variable_set k,v if instance_variables.include? k
         | 
| 28 | 
            +
                	end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
            	
         | 
| 31 | 
            +
                # Gets the adapter name (examples: postgres, mysql)
         | 
| 32 | 
            +
                def self.adapter
         | 
| 33 | 
            +
                  a = name.split("::").last.sub("Adapter","").downcase
         | 
| 34 | 
            +
            			a = "abstract" if a.empty?
         | 
| 35 | 
            +
            			a
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
            		
         | 
| 38 | 
            +
            		def adapter
         | 
| 39 | 
            +
            			self.class.adapter
         | 
| 40 | 
            +
            		end
         | 
| 41 | 
            +
                
         | 
| 42 | 
            +
                # Map a block to an adapter's block hash
         | 
| 43 | 
            +
                #   This avoids cases where a subclass' class var
         | 
| 44 | 
            +
                #   changes that class var on its superclass
         | 
| 45 | 
            +
                def self.map_block(key, block)
         | 
| 46 | 
            +
            			@@blocks[adapter] ||= {}
         | 
| 47 | 
            +
                	@@blocks[adapter][key] = block
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
                
         | 
| 50 | 
            +
                # Fetch a block for the current adapter
         | 
| 51 | 
            +
                def block_for(key)
         | 
| 52 | 
            +
                	b = @@blocks[adapter][key] rescue nil
         | 
| 53 | 
            +
            			b ||= @@blocks["abstract"][key] rescue nil
         | 
| 54 | 
            +
            			b ||= lambda { "" }
         | 
| 55 | 
            +
            			b
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
                
         | 
| 58 | 
            +
                #
         | 
| 59 | 
            +
                # Adapter DSL
         | 
| 60 | 
            +
                # Implemented using method_missing
         | 
| 61 | 
            +
                #
         | 
| 62 | 
            +
                
         | 
| 63 | 
            +
                # example
         | 
| 64 | 
            +
                # execute do |inst,sql|
         | 
| 65 | 
            +
                #   inst.exec sql
         | 
| 66 | 
            +
                # end
         | 
| 67 | 
            +
                
         | 
| 68 | 
            +
                # execute(inst,sql)
         | 
| 69 | 
            +
                # Executes SQL and returns Ruby types
         | 
| 70 | 
            +
                
         | 
| 71 | 
            +
                # TODO: different kinds of escaping: ident & quote
         | 
| 72 | 
            +
                
         | 
| 73 | 
            +
                # sanitize(inst,sql)
         | 
| 74 | 
            +
                # Escapes a literal
         | 
| 75 | 
            +
                
         | 
| 76 | 
            +
                # connect(opts)
         | 
| 77 | 
            +
                # Creates an instance of the DB connection
         | 
| 78 | 
            +
                
         | 
| 79 | 
            +
                # disconnect(inst)
         | 
| 80 | 
            +
                # Disconnects and destroys inst (a database connection)
         | 
| 81 | 
            +
                
         | 
| 82 | 
            +
                # validate(inst)
         | 
| 83 | 
            +
                # Checks if inst is a valid connection
         | 
| 84 | 
            +
             | 
| 85 | 
            +
            		# Map missing methods to blocks
         | 
| 86 | 
            +
            		# DB ops or SQL generation
         | 
| 87 | 
            +
            		def self.method_missing(meth, *args, &block)
         | 
| 88 | 
            +
            			map_block meth, block
         | 
| 89 | 
            +
            		end
         | 
| 90 | 
            +
            		
         | 
| 91 | 
            +
                #
         | 
| 92 | 
            +
                # Adapter methods
         | 
| 93 | 
            +
                #   These are called internally
         | 
| 94 | 
            +
                #   They are called by class methods
         | 
| 95 | 
            +
                #   in ../mystic.rb
         | 
| 96 | 
            +
                #
         | 
| 97 | 
            +
            		def connect(opts)
         | 
| 98 | 
            +
            			disconnect unless @pool.nil?
         | 
| 99 | 
            +
            			@pool = AccessStack.new(
         | 
| 100 | 
            +
            				:size => @pool_size,
         | 
| 101 | 
            +
            				:timeout => @pool_timeout,
         | 
| 102 | 
            +
            				:expires => @pool_expires,
         | 
| 103 | 
            +
            				:create => lambda { block_for(:connect).call opts }
         | 
| 104 | 
            +
            			)
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
              
         | 
| 107 | 
            +
                def disconnect
         | 
| 108 | 
            +
                	@pool.destroy ||= block_for :disconnect
         | 
| 109 | 
            +
            			@pool.empty!
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
                
         | 
| 112 | 
            +
                def reap
         | 
| 113 | 
            +
                	@pool.validate ||= block_for :validate
         | 
| 114 | 
            +
                	@pool.reap
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
              
         | 
| 117 | 
            +
                def execute(sql)
         | 
| 118 | 
            +
            			raise AdapterError, "Adapter's connection pool doesn't exist and so Mystic has not connected to the database." if @pool.nil?
         | 
| 119 | 
            +
            			@pool.with { |inst| block_for(:execute).call inst, sql }
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
              
         | 
| 122 | 
            +
                def sanitize(str)
         | 
| 123 | 
            +
            			@pool.with { |inst| block_for(:sanitize).call inst, str }
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
            		
         | 
| 126 | 
            +
            		def json_supported?
         | 
| 127 | 
            +
            			block_for(:json_supported).call
         | 
| 128 | 
            +
            		end
         | 
| 129 | 
            +
              
         | 
| 130 | 
            +
                def serialize_sql(obj)
         | 
| 131 | 
            +
            			return case obj
         | 
| 132 | 
            +
            			when SQL::Table
         | 
| 133 | 
            +
            				block_for(:table).call obj
         | 
| 134 | 
            +
            			when SQL::Index
         | 
| 135 | 
            +
            				block_for(:index).call obj
         | 
| 136 | 
            +
            			when SQL::Column
         | 
| 137 | 
            +
            				block_for(:column).call obj
         | 
| 138 | 
            +
            			when SQL::Operation
         | 
| 139 | 
            +
            				res = block_for(obj.kind).call obj
         | 
| 140 | 
            +
            				obj.callback.call unless obj.callback.nil?
         | 
| 141 | 
            +
            				res
         | 
| 142 | 
            +
            			end
         | 
| 143 | 
            +
                end
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
            end
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "mystic"
         | 
| 4 | 
            +
            require "mystic/adapter"
         | 
| 5 | 
            +
            require "mystic/sql"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            #
         | 
| 8 | 
            +
            # This adapter is designed to hold
         | 
| 9 | 
            +
            # basic, spec-adherent SQL generation
         | 
| 10 | 
            +
            #	so adapters can be more DRY
         | 
| 11 | 
            +
            #
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            module Mystic
         | 
| 14 | 
            +
            	class Adapter
         | 
| 15 | 
            +
            		
         | 
| 16 | 
            +
            		json_supported { false }
         | 
| 17 | 
            +
            		
         | 
| 18 | 
            +
            		table do |obj|
         | 
| 19 | 
            +
            			sql = []
         | 
| 20 | 
            +
                  sql.push "CREATE TABLE #{obj.name} (#{obj.columns.map(&:to_sql)*","})" if obj.create?
         | 
| 21 | 
            +
                  sql.push "ALTER TABLE #{obj.name} #{obj.columns.map{|c| "ADD COLUMN #{c.to_sql}" }*', '}" unless obj.create?
         | 
| 22 | 
            +
            			sql.push *(obj.indeces.map(&:to_sql)) unless obj.indeces.empty?
         | 
| 23 | 
            +
            	    sql.push *(obj.operations.map(&:to_sql)) unless obj.operations.empty?
         | 
| 24 | 
            +
            			sql*"; "
         | 
| 25 | 
            +
            		end
         | 
| 26 | 
            +
            		
         | 
| 27 | 
            +
            		column do |obj|
         | 
| 28 | 
            +
            			sql = []
         | 
| 29 | 
            +
            			sql << obj.name
         | 
| 30 | 
            +
            			sql << obj.kind.downcase
         | 
| 31 | 
            +
            			sql << "(#{obj.size})" if obj.size && !obj.size.empty? && !obj.geospatial?
         | 
| 32 | 
            +
            			sql << "(#{obj.geom_kind}, #{obj.geom_srid})" if obj.geospatial?
         | 
| 33 | 
            +
                  sql << (obj.constraints[:null] ? "NULL" : "NOT NULL") if obj.constraints.member?(:null)
         | 
| 34 | 
            +
            			sql << "UNIQUE" if obj.constraints[:unique]
         | 
| 35 | 
            +
            			sql << "PRIMARY KEY" if obj.constraints[:primary_key]
         | 
| 36 | 
            +
            			sql << "REFERENCES " + obj.constraints[:references] if obj.constraints.member?(:references)
         | 
| 37 | 
            +
            			sql << "DEFAULT " + obj.constraints[:default] if obj.constraints.member?(:default)
         | 
| 38 | 
            +
            			sql << "CHECK(#{obj.constraints[:check]})" if obj.constraints.member?(:check)
         | 
| 39 | 
            +
            			sql*" "
         | 
| 40 | 
            +
            		end
         | 
| 41 | 
            +
            		
         | 
| 42 | 
            +
            		index do |index|
         | 
| 43 | 
            +
            			sql = []
         | 
| 44 | 
            +
            			sql << "CREATE"
         | 
| 45 | 
            +
            			sql << "UNIQUE" if index.unique
         | 
| 46 | 
            +
            			sql << "INDEX"
         | 
| 47 | 
            +
            		  sql << index.name if index.name
         | 
| 48 | 
            +
            		  sql << "ON"
         | 
| 49 | 
            +
            			sql << index.table_name
         | 
| 50 | 
            +
            			sql << "(#{index.columns.map(&:to_s).join(',')})"
         | 
| 51 | 
            +
            			sql*" "
         | 
| 52 | 
            +
            		end
         | 
| 53 | 
            +
            		
         | 
| 54 | 
            +
            		#
         | 
| 55 | 
            +
            		## Operations
         | 
| 56 | 
            +
            		#
         | 
| 57 | 
            +
            		
         | 
| 58 | 
            +
            		drop_columns do |obj|
         | 
| 59 | 
            +
            			"ALTER TABLE #{obj.table_name} #{obj.column_names.map{|c| "DROP COLUMN #{c.to_s}" }*', '}"
         | 
| 60 | 
            +
            		end
         | 
| 61 | 
            +
            		
         | 
| 62 | 
            +
            		rename_column do |obj|
         | 
| 63 | 
            +
            			"ALTER TABLE #{obj.table_name} RENAME COLUMN #{obj.old_name} TO #{obj.new_name}"
         | 
| 64 | 
            +
            		end
         | 
| 65 | 
            +
            		
         | 
| 66 | 
            +
            		create_view do |obj|
         | 
| 67 | 
            +
            			"CREATE VIEW #{obj.name} AS #{obj.sql}"
         | 
| 68 | 
            +
            		end
         | 
| 69 | 
            +
            		
         | 
| 70 | 
            +
            		drop_view do |obj|
         | 
| 71 | 
            +
            			"DROP VIEW #{obj.name}"
         | 
| 72 | 
            +
            		end
         | 
| 73 | 
            +
            		
         | 
| 74 | 
            +
            		drop_table do |obj|
         | 
| 75 | 
            +
            			"DROP TABLE #{obj.table_name}"
         | 
| 76 | 
            +
            		end
         | 
| 77 | 
            +
            		
         | 
| 78 | 
            +
            		rename_table do |obj|
         | 
| 79 | 
            +
            			"ALTER TABLE #{obj.old_name} RENAME TO #{obj.new_name}"
         | 
| 80 | 
            +
            		end
         | 
| 81 | 
            +
            		
         | 
| 82 | 
            +
            		#
         | 
| 83 | 
            +
            		## Transaction Operations
         | 
| 84 | 
            +
            		#
         | 
| 85 | 
            +
            		
         | 
| 86 | 
            +
            		start_transaction { "BEGIN" }
         | 
| 87 | 
            +
            		commit_transaction { "COMMIT" }
         | 
| 88 | 
            +
            		rollback_transaction { "ROLLBACK" }
         | 
| 89 | 
            +
            	end
         | 
| 90 | 
            +
            end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "mysql2"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            # TODO:
         | 
| 6 | 
            +
            # 1. Implement geometry
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Mystic
         | 
| 9 | 
            +
            	class MysqlAdapter < Mystic::Adapter
         | 
| 10 | 
            +
            		ALGORITHMS = [:default, :inplace, :copy]
         | 
| 11 | 
            +
            		LOCKS = [:default, :none, :shared, :exclusive]
         | 
| 12 | 
            +
            		
         | 
| 13 | 
            +
            		connect { |opts| Mysql2::client.new opts }
         | 
| 14 | 
            +
            		disconnect { |mysql| mysql.close }
         | 
| 15 | 
            +
            		validate { |mysql| mysql.ping }
         | 
| 16 | 
            +
            		execute { |mysql, sql| mysql.query(sql).to_a }
         | 
| 17 | 
            +
            		sanitize { |mysql, str| mysql.escape str }
         | 
| 18 | 
            +
            		
         | 
| 19 | 
            +
            		drop_index do |index|
         | 
| 20 | 
            +
            			"DROP INDEX #{index.name} ON #{index.table_name}"
         | 
| 21 | 
            +
            		end
         | 
| 22 | 
            +
            		
         | 
| 23 | 
            +
            		index do |index|
         | 
| 24 | 
            +
            			sql = []
         | 
| 25 | 
            +
            			sql << "CREATE"
         | 
| 26 | 
            +
            			sql << "UNIQUE" if index.unique
         | 
| 27 | 
            +
            			sql << "INDEX"
         | 
| 28 | 
            +
            		  sql << index.name if index.name
         | 
| 29 | 
            +
            		  sql << "ON"
         | 
| 30 | 
            +
            			sql << index.table_name
         | 
| 31 | 
            +
            			sql << "USING #{obj.type.to_s.capitalize}" if index.type
         | 
| 32 | 
            +
            			sql << "(#{index.columns.map(&:to_s).join ',' })"
         | 
| 33 | 
            +
            			sql << "COMMENT #{index.comment.truncate 1024}" if index.comment
         | 
| 34 | 
            +
            			sql << "ALGORITHM #{index.algorithm.to_s.upcase}" if !index.lock && ALGORITHMS.include? index.algorithm
         | 
| 35 | 
            +
            			sql << "LOCK #{index.lock.to_s.upcase}" if !index.algorithm && LOCKS.include? index.lock
         | 
| 36 | 
            +
            			sql*" "
         | 
| 37 | 
            +
            		end
         | 
| 38 | 
            +
            		
         | 
| 39 | 
            +
            		# Leverage MySQL savepoints to make up for transactions' flaws
         | 
| 40 | 
            +
            		start_transaction { <<-sql
         | 
| 41 | 
            +
            			BEGIN;
         | 
| 42 | 
            +
            			SAVEPOINT mystic_migration8889;
         | 
| 43 | 
            +
            			DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK TO mystic_migration8889;
         | 
| 44 | 
            +
            			DECLARE EXIT HANDLER FOR NOT FOUND ROLLBACK TO mystic_migration8889;
         | 
| 45 | 
            +
            			DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK TO mystic_migration8889;
         | 
| 46 | 
            +
            			sql
         | 
| 47 | 
            +
            		}
         | 
| 48 | 
            +
            		commit_transaction { "RELEASE SAVEPOINT mystic_migration8889;COMMIT" }
         | 
| 49 | 
            +
            		rollback_transaction { "ROLLBACK TO mystic_migration8889; RELEASE SAVEPOINT mystic_migration8889; ROLLBACK" }
         | 
| 50 | 
            +
            	end
         | 
| 51 | 
            +
            end
         | 
| @@ -0,0 +1,95 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "mystic/adapters/abstract"
         | 
| 4 | 
            +
            require "pg"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # Mystic adapter for Postgres, includes PostGIS
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module Mystic
         | 
| 9 | 
            +
            	class PostgresAdapter < Mystic::Adapter
         | 
| 10 | 
            +
            		INDEX_TYPES = [
         | 
| 11 | 
            +
            			:btree,
         | 
| 12 | 
            +
            			:hash,
         | 
| 13 | 
            +
            			:gist,
         | 
| 14 | 
            +
            			:spgist,
         | 
| 15 | 
            +
            			:gin
         | 
| 16 | 
            +
            		].freeze
         | 
| 17 | 
            +
            		
         | 
| 18 | 
            +
            		CONNECT_FIELDS = [
         | 
| 19 | 
            +
            			:host,
         | 
| 20 | 
            +
            			:hostaddr,
         | 
| 21 | 
            +
            			:port,
         | 
| 22 | 
            +
            			:dbname,
         | 
| 23 | 
            +
            			:user,
         | 
| 24 | 
            +
            			:password,
         | 
| 25 | 
            +
            			:connect_timeout,
         | 
| 26 | 
            +
            			:options,
         | 
| 27 | 
            +
            			:tty,
         | 
| 28 | 
            +
            			:sslmode,
         | 
| 29 | 
            +
            			:krbsrvname,
         | 
| 30 | 
            +
            			:gsslib
         | 
| 31 | 
            +
            		].freeze
         | 
| 32 | 
            +
            		
         | 
| 33 | 
            +
            		connect { |opts| PG.connect opts.subhash(*CONNECT_FIELDS) }
         | 
| 34 | 
            +
            		disconnect { |pg| pg.close }
         | 
| 35 | 
            +
            		validate { |pg| pg.status == CONNECTION_OK }
         | 
| 36 | 
            +
            		sanitize { |pg, str| pg.escape_string str }
         | 
| 37 | 
            +
            		json_supported { true }
         | 
| 38 | 
            +
            		
         | 
| 39 | 
            +
            		execute do |pg, sql|
         | 
| 40 | 
            +
            			res = pg.exec sql
         | 
| 41 | 
            +
            			v = res[0][Mystic::JSON_COL] if res.ntuples == 1 && res.nfields == 1
         | 
| 42 | 
            +
            			v ||= res.ntuples.times.map { |i| res[i] } unless res.nil?
         | 
| 43 | 
            +
            			v ||= []
         | 
| 44 | 
            +
            			v
         | 
| 45 | 
            +
            		end
         | 
| 46 | 
            +
            		
         | 
| 47 | 
            +
            		drop_index do |index| 
         | 
| 48 | 
            +
            			"DROP INDEX #{index.index_name}"
         | 
| 49 | 
            +
            		end
         | 
| 50 | 
            +
            		
         | 
| 51 | 
            +
            		create_ext do |ext| 
         | 
| 52 | 
            +
            			"CREATE EXTENSION \"#{ext.name}\""
         | 
| 53 | 
            +
            		end
         | 
| 54 | 
            +
            		
         | 
| 55 | 
            +
            		drop_ext do |ext|
         | 
| 56 | 
            +
            			"DROP EXTENSION \"#{ext.name}\"" 
         | 
| 57 | 
            +
            		end
         | 
| 58 | 
            +
            		
         | 
| 59 | 
            +
            		index do |index|
         | 
| 60 | 
            +
            			storage_params = index.opts.subhash :fillfactor, :buffering, :fastupdate
         | 
| 61 | 
            +
            			
         | 
| 62 | 
            +
            			sql = []
         | 
| 63 | 
            +
            			sql << "CREATE"
         | 
| 64 | 
            +
            			sql << "UNIQUE" if index.unique
         | 
| 65 | 
            +
            			sql << "INDEX"
         | 
| 66 | 
            +
            			sql << "CONCURENTLY" if index.concurrently
         | 
| 67 | 
            +
            		  sql << index.name unless index.name.nil?
         | 
| 68 | 
            +
            		  sql << "ON #{index.table_name}"
         | 
| 69 | 
            +
            			sql << "USING #{index.type}" if INDEX_TYPES.include? index.type
         | 
| 70 | 
            +
            			sql << "(#{index.columns.map(&:to_s).join ',' })"
         | 
| 71 | 
            +
            			sql << "WITH (#{storage_params.sqlize})" unless storage_params.empty?
         | 
| 72 | 
            +
            			sql << "TABLESPACE #{index.tablespace}" unless index.tablespace.nil?
         | 
| 73 | 
            +
            			sql << "WHERE #{index.where}" unless index.where.nil?
         | 
| 74 | 
            +
            			sql*' '
         | 
| 75 | 
            +
            		end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            		table do |table|
         | 
| 78 | 
            +
            			sql = []
         | 
| 79 | 
            +
            			
         | 
| 80 | 
            +
            			if table.create?
         | 
| 81 | 
            +
            				tbl = []
         | 
| 82 | 
            +
            				tbl << "CREATE TABLE #{table.name} (#{table.columns.map(&:to_sql)*","})"
         | 
| 83 | 
            +
            				tbl << "INHERITS #{table.inherits}" if table.inherits
         | 
| 84 | 
            +
            				tbl << "TABLESPACE #{table.tablespace}" if table.tablespace
         | 
| 85 | 
            +
            				sql << tbl*' '
         | 
| 86 | 
            +
            			else
         | 
| 87 | 
            +
            				sql << "ALTER TABLE #{table.name} #{table.columns.map{ |c| "ADD COLUMN #{c.to_sql}" }*', ' }"
         | 
| 88 | 
            +
            			end
         | 
| 89 | 
            +
                  
         | 
| 90 | 
            +
            			sql.push(*table.indeces.map(&:to_sql)) unless table.indeces.empty?
         | 
| 91 | 
            +
            	    sql.push(*table.operations.map(&:to_sql)) unless table.operations.empty?
         | 
| 92 | 
            +
            			sql*'; '
         | 
| 93 | 
            +
            		end
         | 
| 94 | 
            +
            	end
         | 
| 95 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Mystic
         | 
| 4 | 
            +
            	MysticError = Class.new StandardError
         | 
| 5 | 
            +
            	RootError = Class.new StandardError
         | 
| 6 | 
            +
            	EnvironmentError = Class.new StandardError
         | 
| 7 | 
            +
            	AdapterError = Class.new StandardError
         | 
| 8 | 
            +
            	CLIError = Class.new StandardError
         | 
| 9 | 
            +
            	MIG_REGEX = /(?<num>\d+)_(?<name>[a-z]+)\.rb$/i # matches migration files (ex '1_MigrationClassName.rb')
         | 
| 10 | 
            +
            	JSON_COL = "mystic_return_json89788"
         | 
| 11 | 
            +
            end
         | 
| @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Kernel
         | 
| 4 | 
            +
            	def self.silent
         | 
| 5 | 
            +
            		v = $VERBOSE
         | 
| 6 | 
            +
            		$VERBOSE = false
         | 
| 7 | 
            +
            		yield
         | 
| 8 | 
            +
            		$VERBOSE = v
         | 
| 9 | 
            +
            		nil
         | 
| 10 | 
            +
            	end
         | 
| 11 | 
            +
            end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            class String
         | 
| 14 | 
            +
              def desnake
         | 
| 15 | 
            +
                downcase.split("_").map(&:capitalize)*' '
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            	
         | 
| 18 | 
            +
              def sanitize
         | 
| 19 | 
            +
                Mystic.sanitize(self).untaint
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            	
         | 
| 22 | 
            +
            	def truncate(len)
         | 
| 23 | 
            +
            		self[0..len-1]
         | 
| 24 | 
            +
            	end
         | 
| 25 | 
            +
            	
         | 
| 26 | 
            +
            	def sql_terminate
         | 
| 27 | 
            +
            		return self + ";" unless dup.strip.end_with? ";"
         | 
| 28 | 
            +
            		self
         | 
| 29 | 
            +
            	end
         | 
| 30 | 
            +
            end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            class Array
         | 
| 33 | 
            +
              def merge_keys(*keys)
         | 
| 34 | 
            +
            		raise ArgumentError, "No keys to merge." if keys.nil? || keys.empty?
         | 
| 35 | 
            +
                raise ArgumentError, "Argument array must have the same number of elements as self." if keys.count != self.count
         | 
| 36 | 
            +
                Hash[each_with_index.map{ |v,i| [keys[i],v] }]
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            	
         | 
| 39 | 
            +
            	def symbolize
         | 
| 40 | 
            +
            		map(&:to_sym)
         | 
| 41 | 
            +
            	end
         | 
| 42 | 
            +
            	
         | 
| 43 | 
            +
            	def symbolize!
         | 
| 44 | 
            +
            		map!(&:to_sym)
         | 
| 45 | 
            +
            	end
         | 
| 46 | 
            +
            	
         | 
| 47 | 
            +
            	def sqlize
         | 
| 48 | 
            +
            		map { |o|
         | 
| 49 | 
            +
            			case o
         | 
| 50 | 
            +
            			when String
         | 
| 51 | 
            +
            				"'#{o.sanitize}'"
         | 
| 52 | 
            +
            			when Numeric
         | 
| 53 | 
            +
            				o.to_s
         | 
| 54 | 
            +
            			end
         | 
| 55 | 
            +
            		}.compact
         | 
| 56 | 
            +
            	end
         | 
| 57 | 
            +
            end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            class Hash
         | 
| 60 | 
            +
            	def subhash(*keys)
         | 
| 61 | 
            +
            		Hash[values_at(*keys).merge_keys(*keys).reject{ |k,v| v.nil? }]
         | 
| 62 | 
            +
            	end
         | 
| 63 | 
            +
            	
         | 
| 64 | 
            +
              def parify(delim=" ")
         | 
| 65 | 
            +
                map { |pair| pair * delim }
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
            	
         | 
| 68 | 
            +
            	def compact
         | 
| 69 | 
            +
            		reject { |k,v| v.nil? }
         | 
| 70 | 
            +
            	end
         | 
| 71 | 
            +
            	
         | 
| 72 | 
            +
            	def compact!
         | 
| 73 | 
            +
            		reject! { |k,v| v.nil? }
         | 
| 74 | 
            +
            	end
         | 
| 75 | 
            +
            	
         | 
| 76 | 
            +
            	def symbolize
         | 
| 77 | 
            +
            		Hash[map { |k,v| [k.to_sym, v]}]
         | 
| 78 | 
            +
            	end
         | 
| 79 | 
            +
            	
         | 
| 80 | 
            +
            	def symbolize!
         | 
| 81 | 
            +
            		keys.each { |key| self[key.to_sym] = delete key }
         | 
| 82 | 
            +
            	end
         | 
| 83 | 
            +
              
         | 
| 84 | 
            +
              def sqlize
         | 
| 85 | 
            +
                reject { |k,v| v.nil? || v.empty? }.map{ |k,v| "#{k}=#{Integer === v ? v : "'#{v.to_s.sanitize}'" }" }
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            class Pathname
         | 
| 90 | 
            +
            	Kernel.silent do
         | 
| 91 | 
            +
            	  def relative?
         | 
| 92 | 
            +
            	    @path[0] != File::SEPARATOR
         | 
| 93 | 
            +
            	  end
         | 
| 94 | 
            +
            	
         | 
| 95 | 
            +
            		def join(*args)
         | 
| 96 | 
            +
            			Pathname.new(File.join @path, *args.map(&:to_s))
         | 
| 97 | 
            +
            		end
         | 
| 98 | 
            +
            	end
         | 
| 99 | 
            +
            end
         | 
| @@ -0,0 +1,108 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Mystic  
         | 
| 4 | 
            +
              class Migration
         | 
| 5 | 
            +
            		Error = Class.new(StandardError)
         | 
| 6 | 
            +
            		IrreversibleError = Class.new(StandardError)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            		def initialize
         | 
| 9 | 
            +
            			@irreversible = false
         | 
| 10 | 
            +
            			@sql = ""
         | 
| 11 | 
            +
            		end
         | 
| 12 | 
            +
            		
         | 
| 13 | 
            +
            		def migrate
         | 
| 14 | 
            +
            			exec_migration :up
         | 
| 15 | 
            +
            		end
         | 
| 16 | 
            +
            		
         | 
| 17 | 
            +
            		def rollback
         | 
| 18 | 
            +
            			exec_migration :down
         | 
| 19 | 
            +
            		end
         | 
| 20 | 
            +
            		
         | 
| 21 | 
            +
            		# TODO: This is ugly... It needs cleaning up.
         | 
| 22 | 
            +
            		def exec_migration(direction)
         | 
| 23 | 
            +
            			@sql = ""
         | 
| 24 | 
            +
            			
         | 
| 25 | 
            +
            			direction = direction.to_sym
         | 
| 26 | 
            +
            			
         | 
| 27 | 
            +
            			raise ArgumentError, "Direction must be either :up or :down." unless [:up, :down].include? direction
         | 
| 28 | 
            +
            			raise IrreversibleError, "Impossible to roll back an irreversible migration." if direction == :down && irreversible?
         | 
| 29 | 
            +
            			
         | 
| 30 | 
            +
            			execute Mystic::SQL::Operation.start_transaction
         | 
| 31 | 
            +
            			method(direction).call
         | 
| 32 | 
            +
            			execute Mystic::SQL::Operation.commit_transaction
         | 
| 33 | 
            +
            			
         | 
| 34 | 
            +
            			Mystic.adapter.execute @sql # bypass densification
         | 
| 35 | 
            +
            		end
         | 
| 36 | 
            +
            		
         | 
| 37 | 
            +
            		
         | 
| 38 | 
            +
            		#
         | 
| 39 | 
            +
            		# DSL
         | 
| 40 | 
            +
            		#
         | 
| 41 | 
            +
            		
         | 
| 42 | 
            +
            		# All migration SQL goes through here
         | 
| 43 | 
            +
                def execute(obj)
         | 
| 44 | 
            +
            			@sql << obj.to_s.sql_terminate # to_sql isn't defined for strings, to_sql is aliased to to_s
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
            		
         | 
| 47 | 
            +
            		def irreversible!
         | 
| 48 | 
            +
            			@irreversible = true
         | 
| 49 | 
            +
            		end
         | 
| 50 | 
            +
            		
         | 
| 51 | 
            +
            		def irreversible?
         | 
| 52 | 
            +
            			@irreversible
         | 
| 53 | 
            +
            		end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def create_table(name)
         | 
| 56 | 
            +
                  raise ArgumentError, "No block provided, a block is required to create a table." unless block_given?
         | 
| 57 | 
            +
                  table = Mystic::SQL::Table.create :name => name
         | 
| 58 | 
            +
                  yield table
         | 
| 59 | 
            +
                  execute table
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
                
         | 
| 62 | 
            +
                def alter_table(name)
         | 
| 63 | 
            +
            			raise ArgumentError, "No block provided, a block is required to alter a table." unless block_given?
         | 
| 64 | 
            +
                  table = Mystic::SQL::Table.alter :name => name
         | 
| 65 | 
            +
                  yield table
         | 
| 66 | 
            +
                  execute table
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
                
         | 
| 69 | 
            +
                def drop_table(name)
         | 
| 70 | 
            +
            			irreversible!
         | 
| 71 | 
            +
            			execute Mystic::SQL::Operation.drop_table(
         | 
| 72 | 
            +
            				:table_name => name.to_s
         | 
| 73 | 
            +
            			)
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
            		
         | 
| 76 | 
            +
            		def drop_index(*args)
         | 
| 77 | 
            +
            			execute Mystic::SQL::Operation.drop_index(
         | 
| 78 | 
            +
            				:index_name => args[0],
         | 
| 79 | 
            +
            				:table_name => args[1]
         | 
| 80 | 
            +
            			)
         | 
| 81 | 
            +
            		end
         | 
| 82 | 
            +
                
         | 
| 83 | 
            +
                def create_ext(extname)
         | 
| 84 | 
            +
            			execute Mystic::SQL::Operation.create_ext(
         | 
| 85 | 
            +
            				:name => extname.to_s
         | 
| 86 | 
            +
            			)
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
                
         | 
| 89 | 
            +
                def drop_ext(extname)
         | 
| 90 | 
            +
            			execute Mystic::SQL::Operation.drop_ext(
         | 
| 91 | 
            +
            				:name => extname.to_s
         | 
| 92 | 
            +
            			)
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
                
         | 
| 95 | 
            +
                def create_view(name, sql)
         | 
| 96 | 
            +
            			execute Mystic::SQL::Operation.create_view(
         | 
| 97 | 
            +
            				:name => name.to_s,
         | 
| 98 | 
            +
            				:sql => sql.to_s
         | 
| 99 | 
            +
            			)
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
                
         | 
| 102 | 
            +
                def drop_view(name)
         | 
| 103 | 
            +
            			execute Mystic::SQL::Operation.drop_view(
         | 
| 104 | 
            +
            				:name => name.to_s
         | 
| 105 | 
            +
            			)
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
            end
         | 
    
        data/lib/mystic/model.rb
    ADDED
    
    | @@ -0,0 +1,130 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Mystic
         | 
| 4 | 
            +
              class Model
         | 
| 5 | 
            +
                def self.table_name
         | 
| 6 | 
            +
                  to_s.downcase
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
            		
         | 
| 9 | 
            +
            		def self.visible_cols
         | 
| 10 | 
            +
            			["*"]
         | 
| 11 | 
            +
            		end
         | 
| 12 | 
            +
            		
         | 
| 13 | 
            +
            		def self.wrapper_sql(opts={})
         | 
| 14 | 
            +
            			sym_opts = opts.symbolize
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            			sql = sym_opts[:sql] || "SELECT 1"
         | 
| 17 | 
            +
            			return_rows = sym_opts[:return_rows] || false
         | 
| 18 | 
            +
            			return_json = sym_opts[:return_json] || false
         | 
| 19 | 
            +
            			return_rows = true if return_json
         | 
| 20 | 
            +
            			
         | 
| 21 | 
            +
            			op = sql.split(/\s+/,2).first
         | 
| 22 | 
            +
            			
         | 
| 23 | 
            +
            			sql << " RETURNING #{visible_cols*','}" if return_rows && op != "SELECT"
         | 
| 24 | 
            +
            			
         | 
| 25 | 
            +
            			s = []
         | 
| 26 | 
            +
            			
         | 
| 27 | 
            +
            			if return_json
         | 
| 28 | 
            +
            				s << "WITH res AS (#{sql}) SELECT"
         | 
| 29 | 
            +
            				s << "row_to_json(res)" if op == "INSERT"
         | 
| 30 | 
            +
            				s << "array_to_json(array_agg(res))" unless op == "INSERT"
         | 
| 31 | 
            +
            				s << "AS #{Mystic::JSON_COL}"
         | 
| 32 | 
            +
            				s << "FROM res"
         | 
| 33 | 
            +
            			else
         | 
| 34 | 
            +
            				s << sql
         | 
| 35 | 
            +
            			end
         | 
| 36 | 
            +
            			
         | 
| 37 | 
            +
            			s*' '
         | 
| 38 | 
            +
            		end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def self.function_sql(returns_rows, funcname, *params)
         | 
| 41 | 
            +
            			"SELECT #{returns_rows ? "* FROM" : ""} #{funcname}(#{params.sqlize*','})"
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
                
         | 
| 44 | 
            +
                def self.select_sql(params={}, opts={})
         | 
| 45 | 
            +
            			sym_opts = opts.symbolize
         | 
| 46 | 
            +
                  count = sym_opts[:count] || 0
         | 
| 47 | 
            +
            			where = params.sqlize
         | 
| 48 | 
            +
            			
         | 
| 49 | 
            +
            			sql = []
         | 
| 50 | 
            +
            			sql << "SELECT #{visible_cols*','} FROM #{table_name}"
         | 
| 51 | 
            +
            			sql << "WHERE #{where*' AND '}" if where.count > 0
         | 
| 52 | 
            +
            			sql << "LIMIT #{count.to_i}" if count > 0
         | 
| 53 | 
            +
            			
         | 
| 54 | 
            +
            			wrapper_sql(
         | 
| 55 | 
            +
            				:sql => sql.join(' '),
         | 
| 56 | 
            +
            				:return_rows => true,
         | 
| 57 | 
            +
            				:return_json => sym_opts[:return_json] && Mystic.adapter.json_supported?
         | 
| 58 | 
            +
            			)
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
                
         | 
| 61 | 
            +
                def self.update_sql(where={}, set={}, opts={})
         | 
| 62 | 
            +
                  return "" if where.empty?
         | 
| 63 | 
            +
                  return "" if set.empty?
         | 
| 64 | 
            +
            			
         | 
| 65 | 
            +
            			sym_opts = opts.symbolize
         | 
| 66 | 
            +
            			
         | 
| 67 | 
            +
            			wrapper_sql(
         | 
| 68 | 
            +
            				:sql => "UPDATE #{table_name} SET #{set.sqlize*','} WHERE #{where.sqlize*' AND '}",
         | 
| 69 | 
            +
            				:return_rows => sym_opts[:return_rows],
         | 
| 70 | 
            +
            				:return_json => sym_opts[:return_json] && Mystic.adapter.json_supported?
         | 
| 71 | 
            +
            			)
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
                
         | 
| 74 | 
            +
                def self.insert_sql(params={}, opts={})
         | 
| 75 | 
            +
            			return "" if params.empty?
         | 
| 76 | 
            +
                  
         | 
| 77 | 
            +
            			sym_opts = opts.symbolize
         | 
| 78 | 
            +
            			
         | 
| 79 | 
            +
            			wrapper_sql(
         | 
| 80 | 
            +
            				:sql => "INSERT INTO #{table_name} (#{params.keys*','}) VALUES (#{params.values.sqlize*','})",
         | 
| 81 | 
            +
            				:return_rows => sym_opts[:return_rows],
         | 
| 82 | 
            +
            				:return_json => sym_opts[:return_json] && Mystic.adapter.json_supported?
         | 
| 83 | 
            +
            			)
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
                
         | 
| 86 | 
            +
                def self.delete_sql(params={}, opts={})
         | 
| 87 | 
            +
                  return "" if params.empty?
         | 
| 88 | 
            +
            			
         | 
| 89 | 
            +
            			sym_opts = opts.symbolize
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            			wrapper_sql(
         | 
| 92 | 
            +
            				:sql => "DELETE FROM #{table_name} WHERE #{params.sqlize*' AND '}",
         | 
| 93 | 
            +
            				:return_rows => sym_opts[:return_rows],
         | 
| 94 | 
            +
            				:return_json => sym_opts[:return_json] && Mystic.adapter.json_supported?
         | 
| 95 | 
            +
            			)
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
                
         | 
| 98 | 
            +
                def self.select(params={}, opts={})
         | 
| 99 | 
            +
                  Mystic.execute select_sql(params, opts)
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
                
         | 
| 102 | 
            +
                def self.fetch(params={}, opts={})
         | 
| 103 | 
            +
                  res = select params, opts.merge({:count => 1})
         | 
| 104 | 
            +
            			return res if res.is_a? String
         | 
| 105 | 
            +
            			res.first rescue nil
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
                
         | 
| 108 | 
            +
                def self.create(params={}, opts={})
         | 
| 109 | 
            +
                  res = Mystic.execute insert_sql(params, opts)
         | 
| 110 | 
            +
            			return res if res.is_a? String
         | 
| 111 | 
            +
            			res.first rescue nil
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
                
         | 
| 114 | 
            +
                def self.update(where={}, set={}, opts={})
         | 
| 115 | 
            +
                  Mystic.execute update_sql(where, set, opts.merge({ :return_rows => true }))
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
                
         | 
| 118 | 
            +
                def self.delete(params={}, opts={})
         | 
| 119 | 
            +
            			Mystic.execute delete_sql(params, opts)
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
            		
         | 
| 122 | 
            +
            		def self.exec_func(funcname, *params)
         | 
| 123 | 
            +
            			Mystic.execute function_sql(false, funcname, *params)
         | 
| 124 | 
            +
            		end
         | 
| 125 | 
            +
            		
         | 
| 126 | 
            +
            		def self.exec_func_rows(funcname, *params)
         | 
| 127 | 
            +
            			Mystic.execute function_sql(true, funcname, *params)
         | 
| 128 | 
            +
            		end
         | 
| 129 | 
            +
              end
         | 
| 130 | 
            +
            end
         | 
    
        data/lib/mystic/sql.rb
    ADDED
    
    | @@ -0,0 +1,244 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Mystic
         | 
| 4 | 
            +
              module SQL
         | 
| 5 | 
            +
            		Error = Class.new(StandardError)
         | 
| 6 | 
            +
            		
         | 
| 7 | 
            +
                class SQLObject
         | 
| 8 | 
            +
                  def to_sql
         | 
| 9 | 
            +
                    Mystic.adapter.serialize_sql self
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                  
         | 
| 12 | 
            +
                  alias_method :to_s, :to_sql
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                class Index < SQLObject
         | 
| 16 | 
            +
                  attr_accessor :name, # Symbol or string
         | 
| 17 | 
            +
            										:table_name, # Symbol or string
         | 
| 18 | 
            +
            										:type, # Symbol
         | 
| 19 | 
            +
            										:unique, # TrueClass/FalseClass
         | 
| 20 | 
            +
            										:columns, # Array of Strings
         | 
| 21 | 
            +
            										:opts # Hash, see below
         | 
| 22 | 
            +
                  
         | 
| 23 | 
            +
            			# opts
         | 
| 24 | 
            +
            			# It's a Hash that represents options
         | 
| 25 | 
            +
            			#
         | 
| 26 | 
            +
            			# MYSQL ONLY
         | 
| 27 | 
            +
            			# Key => Value (type)
         | 
| 28 | 
            +
            			# :comment => A string that's up to 1024 chars (String)
         | 
| 29 | 
            +
            			# :algorithm => The algorithm to use (Symbol)
         | 
| 30 | 
            +
            			# :lock => The lock to use (Symbol)
         | 
| 31 | 
            +
            			#
         | 
| 32 | 
            +
            			# POSTGRES ONLY
         | 
| 33 | 
            +
            			# Key => Value (type)
         | 
| 34 | 
            +
            			# :fillfactor => A value in the range 10..100 (Integer)
         | 
| 35 | 
            +
            			# :fastupdate => true/false (TrueClass/FalseClass)
         | 
| 36 | 
            +
            			# :concurrently => true/false (TrueClass/FalseClass)
         | 
| 37 | 
            +
            			# :tablespace => The name of the desired tablespace (String)
         | 
| 38 | 
            +
            			# :buffering => :on/:off/:auto (Symbol)
         | 
| 39 | 
            +
            			# :concurrently => true/false (TrueClass/FalseClass)
         | 
| 40 | 
            +
            			# :where => The conditions for including entries in your index, same as SELECT * FROM table WHERE ____ (String)
         | 
| 41 | 
            +
            			
         | 
| 42 | 
            +
                  def initialize(opts={})
         | 
| 43 | 
            +
            				opts.symbolize!
         | 
| 44 | 
            +
            				raise ArgumentError, "Indeces need a table_name or else what's the point?." unless opts.member? :table_name
         | 
| 45 | 
            +
            				raise ArgumentError, "Indeces need columns or else what's the point?" unless opts.member? :columns
         | 
| 46 | 
            +
                    @name = opts.delete(:name).to_sym if opts.member? :name
         | 
| 47 | 
            +
                    @table_name = opts.delete(:table_name).to_sym
         | 
| 48 | 
            +
                    @type = (opts.delete :type || :btree).to_s.downcase.to_sym
         | 
| 49 | 
            +
            				@unique = opts.delete :unique || false
         | 
| 50 | 
            +
                    @columns = opts.delete(:columns).symbolize rescue []
         | 
| 51 | 
            +
            				@opts = opts
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                  
         | 
| 54 | 
            +
                  # can accept shit other than columns like
         | 
| 55 | 
            +
                  # box(location,location)
         | 
| 56 | 
            +
                  def <<(col)
         | 
| 57 | 
            +
                    case col
         | 
| 58 | 
            +
                    when Column
         | 
| 59 | 
            +
                      @columns << col.name.to_s
         | 
| 60 | 
            +
                    when String
         | 
| 61 | 
            +
                      @columns << col
         | 
| 62 | 
            +
                    else
         | 
| 63 | 
            +
                      raise ArgumentError, "Column must be a String or a Mystic::SQL::Column"
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  alias_method :push, :<<
         | 
| 68 | 
            +
            			
         | 
| 69 | 
            +
            			def method_missing(meth, *args, &block)
         | 
| 70 | 
            +
            				return @opts[meth] if @opts.member? meth
         | 
| 71 | 
            +
            				nil
         | 
| 72 | 
            +
            			end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
            		
         | 
| 75 | 
            +
                class Column < SQLObject
         | 
| 76 | 
            +
                  attr_accessor :name, :kind, :size, :constraints, :geom_kind, :geom_srid
         | 
| 77 | 
            +
                  
         | 
| 78 | 
            +
                  def initialize(opts={})
         | 
| 79 | 
            +
                    @name = opts.delete(:name).to_s
         | 
| 80 | 
            +
                    @kind = opts.delete(:kind).to_sym
         | 
| 81 | 
            +
                    @size = opts.delete(:size).to_s if opts.member? :size
         | 
| 82 | 
            +
                    @geom_kind = opts.delete(:geom_kind)
         | 
| 83 | 
            +
                    @geom_srid = opts.delete(:geom_srid).to_i
         | 
| 84 | 
            +
                    @constraints = opts
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
                  
         | 
| 87 | 
            +
                  def geospatial?
         | 
| 88 | 
            +
            				@geom_kind && @geom_srid
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                class Table < SQLObject
         | 
| 93 | 
            +
                  attr_reader :name
         | 
| 94 | 
            +
                  attr_accessor :columns,
         | 
| 95 | 
            +
            										:indeces,
         | 
| 96 | 
            +
            										:operations,
         | 
| 97 | 
            +
            										:opts
         | 
| 98 | 
            +
            										
         | 
| 99 | 
            +
            			def self.create(opts={})
         | 
| 100 | 
            +
            				new true, opts
         | 
| 101 | 
            +
            			end
         | 
| 102 | 
            +
            			
         | 
| 103 | 
            +
            			def self.alter(opts={})
         | 
| 104 | 
            +
            				new false, opts
         | 
| 105 | 
            +
            			end
         | 
| 106 | 
            +
                  
         | 
| 107 | 
            +
                  def initialize(is_create=true, opts={})
         | 
| 108 | 
            +
            				@is_create = is_create
         | 
| 109 | 
            +
            				@opts = opts.symbolize
         | 
| 110 | 
            +
                    @columns = []
         | 
| 111 | 
            +
                    @indeces = []
         | 
| 112 | 
            +
                    @operations = []
         | 
| 113 | 
            +
            				
         | 
| 114 | 
            +
                    @name = @opts.delete(:name).to_s
         | 
| 115 | 
            +
                    raise ArgumentError, "Argument 'name' is invalid." if @name.empty?
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
            			
         | 
| 118 | 
            +
            			def create?
         | 
| 119 | 
            +
            				@is_create
         | 
| 120 | 
            +
            			end
         | 
| 121 | 
            +
                
         | 
| 122 | 
            +
                  def <<(obj)
         | 
| 123 | 
            +
                    case obj
         | 
| 124 | 
            +
                    when Column
         | 
| 125 | 
            +
                      @columns << obj
         | 
| 126 | 
            +
                    when Index
         | 
| 127 | 
            +
                      @indeces << obj
         | 
| 128 | 
            +
                    when Operation
         | 
| 129 | 
            +
                      @operations << obj
         | 
| 130 | 
            +
                    else
         | 
| 131 | 
            +
                      raise ArgumentError, "Argument is not a Mystic::SQL::Column, Mystic::SQL::Operation, or Mystic::SQL::Index."
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
                
         | 
| 135 | 
            +
                  def to_sql
         | 
| 136 | 
            +
                    raise ArgumentError, "Table cannot have zero columns." if @columns.empty?
         | 
| 137 | 
            +
                    super
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  alias_method :push, :<<
         | 
| 141 | 
            +
             | 
| 142 | 
            +
            			#
         | 
| 143 | 
            +
            			## Operation DSL
         | 
| 144 | 
            +
            			#
         | 
| 145 | 
            +
            			
         | 
| 146 | 
            +
                  def drop_index(idx_name)
         | 
| 147 | 
            +
            				raise Mystic::SQL::Error, "Cannot drop an index on a table that doesn't exist." if create?
         | 
| 148 | 
            +
                    self << Mystic::SQL::Operation.drop_index(
         | 
| 149 | 
            +
                      :index_name => idx_name.to_s,
         | 
| 150 | 
            +
                      :table_name => self.name.to_s
         | 
| 151 | 
            +
                    )
         | 
| 152 | 
            +
                  end
         | 
| 153 | 
            +
                
         | 
| 154 | 
            +
                  def rename_column(oldname, newname)
         | 
| 155 | 
            +
            				raise Mystic::SQL::Error, "Cannot rename a column on a table that doesn't exist." if create?
         | 
| 156 | 
            +
                    self << Mystic::SQL::Operation.rename_column(
         | 
| 157 | 
            +
                      :table_name => self.name.to_s,
         | 
| 158 | 
            +
                      :old_name => oldname.to_s,
         | 
| 159 | 
            +
                      :new_name => newname.to_s
         | 
| 160 | 
            +
                    )
         | 
| 161 | 
            +
                  end
         | 
| 162 | 
            +
                
         | 
| 163 | 
            +
                  def rename(newname)
         | 
| 164 | 
            +
            				raise Mystic::SQL::Error, "Cannot rename a table that doesn't exist." if create?
         | 
| 165 | 
            +
                    self << Mystic::SQL::Operation.rename_table(
         | 
| 166 | 
            +
                      :old_name => self.name.dup.to_s,
         | 
| 167 | 
            +
                      :new_name => newname.to_s,
         | 
| 168 | 
            +
            					:callback => lambda { self.name = newname }
         | 
| 169 | 
            +
                    )
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
                
         | 
| 172 | 
            +
                  def drop_columns(*col_names)
         | 
| 173 | 
            +
            				raise Mystic::SQL::Error, "Cannot drop a column(s) on a table that doesn't exist." if create?
         | 
| 174 | 
            +
                    self << Mystic::SQL::Operation.drop_columns(
         | 
| 175 | 
            +
                      :table_name => self.name.to_s,
         | 
| 176 | 
            +
                      :column_names => col_names.map(&:to_s)
         | 
| 177 | 
            +
                    )
         | 
| 178 | 
            +
                  end
         | 
| 179 | 
            +
                  
         | 
| 180 | 
            +
                  #
         | 
| 181 | 
            +
                  ## Column DSL
         | 
| 182 | 
            +
                  #
         | 
| 183 | 
            +
                  
         | 
| 184 | 
            +
                  def column(col_name, kind, opts={})
         | 
| 185 | 
            +
                    self << Mystic::SQL::Column.new({
         | 
| 186 | 
            +
                      :name => col_name,
         | 
| 187 | 
            +
                      :kind => kind.to_sym
         | 
| 188 | 
            +
                    }.merge(opts || {}))
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                  def geometry(col_name, kind, srid, opts={})
         | 
| 192 | 
            +
            				self << Mystic::SQL::Column.new({
         | 
| 193 | 
            +
                      :name => col_name,
         | 
| 194 | 
            +
            					:kind => :geometry,
         | 
| 195 | 
            +
                      :geom_kind => kind,
         | 
| 196 | 
            +
                      :geom_srid => srid
         | 
| 197 | 
            +
                    }.merge(opts || {}))
         | 
| 198 | 
            +
                  end
         | 
| 199 | 
            +
                  
         | 
| 200 | 
            +
                  def index(*cols)
         | 
| 201 | 
            +
                    opts = cols.delete_at -1 if cols.last.is_a? Hash
         | 
| 202 | 
            +
                    opts ||= {}
         | 
| 203 | 
            +
                    opts[:columns] = cols
         | 
| 204 | 
            +
            				opts[:table_name] = @name
         | 
| 205 | 
            +
                    self << Mystic::SQL::Index.new(opts)
         | 
| 206 | 
            +
                  end
         | 
| 207 | 
            +
                  
         | 
| 208 | 
            +
                  def method_missing(meth, *args, &block)
         | 
| 209 | 
            +
            				return column args[0], meth.to_s, args[1] if args.count > 0
         | 
| 210 | 
            +
            				return @opts[meth] if @opts.member?(meth)
         | 
| 211 | 
            +
            				nil
         | 
| 212 | 
            +
                  end
         | 
| 213 | 
            +
                end
         | 
| 214 | 
            +
                
         | 
| 215 | 
            +
                class Operation < SQLObject
         | 
| 216 | 
            +
            			attr_reader :kind,
         | 
| 217 | 
            +
            									:callback
         | 
| 218 | 
            +
            			
         | 
| 219 | 
            +
                  def initialize(kind, opts={})
         | 
| 220 | 
            +
            				@kind = kind
         | 
| 221 | 
            +
            				@opts = opts.dup
         | 
| 222 | 
            +
            				@callback = @opts.delete :callback
         | 
| 223 | 
            +
                  end
         | 
| 224 | 
            +
                  
         | 
| 225 | 
            +
                  def method_missing(meth, *args, &block)
         | 
| 226 | 
            +
            				@opts[meth.to_s.to_sym] rescue nil
         | 
| 227 | 
            +
                  end
         | 
| 228 | 
            +
            			
         | 
| 229 | 
            +
            			def self.method_missing(meth, *args, &block)
         | 
| 230 | 
            +
            				new meth, (args[0] || {})
         | 
| 231 | 
            +
            			end
         | 
| 232 | 
            +
                end
         | 
| 233 | 
            +
            		
         | 
| 234 | 
            +
            		class Raw < SQLObject
         | 
| 235 | 
            +
            			def initialize(opts)
         | 
| 236 | 
            +
            				@sql = opts[:sql]
         | 
| 237 | 
            +
            			end
         | 
| 238 | 
            +
            			
         | 
| 239 | 
            +
            			def to_sql
         | 
| 240 | 
            +
            				@sql
         | 
| 241 | 
            +
            			end
         | 
| 242 | 
            +
            		end
         | 
| 243 | 
            +
              end
         | 
| 244 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,99 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: mystic
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Nathaniel Symer
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2014-07-05 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: densify
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: access_stack
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '0'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '0'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: rspec
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '3'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '3'
         | 
| 55 | 
            +
            description: Database management/access gem. Supports adapters, migrations, and a
         | 
| 56 | 
            +
              singleton to make SQL queries.
         | 
| 57 | 
            +
            email: nate@ivytap.com
         | 
| 58 | 
            +
            executables:
         | 
| 59 | 
            +
            - mystic
         | 
| 60 | 
            +
            extensions: []
         | 
| 61 | 
            +
            extra_rdoc_files: []
         | 
| 62 | 
            +
            files:
         | 
| 63 | 
            +
            - LICENSE
         | 
| 64 | 
            +
            - bin/mystic
         | 
| 65 | 
            +
            - lib/mystic.rb
         | 
| 66 | 
            +
            - lib/mystic/adapter.rb
         | 
| 67 | 
            +
            - lib/mystic/adapters/abstract.rb
         | 
| 68 | 
            +
            - lib/mystic/adapters/mysql.rb
         | 
| 69 | 
            +
            - lib/mystic/adapters/postgres.rb
         | 
| 70 | 
            +
            - lib/mystic/constants.rb
         | 
| 71 | 
            +
            - lib/mystic/extensions.rb
         | 
| 72 | 
            +
            - lib/mystic/migration.rb
         | 
| 73 | 
            +
            - lib/mystic/model.rb
         | 
| 74 | 
            +
            - lib/mystic/sql.rb
         | 
| 75 | 
            +
            homepage: https://github.com/ivytap/mystic
         | 
| 76 | 
            +
            licenses:
         | 
| 77 | 
            +
            - MIT
         | 
| 78 | 
            +
            metadata: {}
         | 
| 79 | 
            +
            post_install_message: 
         | 
| 80 | 
            +
            rdoc_options: []
         | 
| 81 | 
            +
            require_paths:
         | 
| 82 | 
            +
            - lib
         | 
| 83 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 84 | 
            +
              requirements:
         | 
| 85 | 
            +
              - - ">="
         | 
| 86 | 
            +
                - !ruby/object:Gem::Version
         | 
| 87 | 
            +
                  version: '0'
         | 
| 88 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 89 | 
            +
              requirements:
         | 
| 90 | 
            +
              - - ">="
         | 
| 91 | 
            +
                - !ruby/object:Gem::Version
         | 
| 92 | 
            +
                  version: '0'
         | 
| 93 | 
            +
            requirements: []
         | 
| 94 | 
            +
            rubyforge_project: 
         | 
| 95 | 
            +
            rubygems_version: 2.2.2
         | 
| 96 | 
            +
            signing_key: 
         | 
| 97 | 
            +
            specification_version: 4
         | 
| 98 | 
            +
            summary: Lightweight migrations + SQL execution
         | 
| 99 | 
            +
            test_files: []
         |