require_dependency 'carto/db/view' require_dependency 'carto/db/function' require_dependency 'carto/db/trigger' require_dependency 'carto/db/role' module Carto module Db class Database def self.build_with_user(user) Database.new(user.database_name, user.in_database) end def initialize(database_name, conn) @database_name = database_name @conn = conn end # Attempts to create a new database schema # Does not raise exception if the schema already exists def create_schema(schema, role = nil) if role @conn.run(%{CREATE SCHEMA "#{schema}" AUTHORIZATION "#{role}"}) else @conn.run(%{CREATE SCHEMA "#{schema}"}) end rescue Sequel::DatabaseError => e raise unless e.message =~ /schema .* already exists/ end def triggers(schema) query = %{ select table_catalog, table_schema, relname, tgname, tgtype, proname, tgdeferrable, tginitdeferred, tgnargs, tgattr, tgargs from (pg_trigger join pg_class on tgrelid=pg_class.oid) join pg_proc on (tgfoid=pg_proc.oid) join information_schema.tables ist on relname = table_name where table_schema = '#{schema}' and table_catalog = '#{@database_name}' } execute_query(@conn, query).map do |t| t = t.deep_symbolize_keys Trigger.new( database_name: t[:table_catalog], database_schema: t[:table_schema], table_name: t[:table_name], trigger_name: t[:tgname] ) end end def materialized_views(schema, owner_role) views(schema, owner_role, 'm') end # relkind: 'm' (materialized view) or 'v' (view). Default: 'v'. def views(schema, owner_role, relkind = 'v') query = %{ select ns.nspname as schemaname, pc.relname as matviewname, string_agg(atr.attname ||' '||pg_catalog.format_type(atr.atttypid, NULL), ', ') as columns from pg_class pc join pg_namespace ns on pc.relnamespace = ns.oid join pg_attribute atr on atr.attrelid = pc.oid and atr.attnum > 0 and not atr.attisdropped join pg_roles on pc.relowner = pg_roles.oid where pc.relkind = '#{relkind}' and ns.nspname = '#{schema}' and rolname = '#{owner_role}' group by ns.nspname, pc.relname; } execute_query(@conn, query).map do |v| v = v.deep_symbolize_keys View.new( database_name: @database_name, database_schema: v[:schemaname], name: v[:matviewname], relkind: relkind ) end end def functions(schema, owner_role) query = %{ SELECT n.nspname, p.proname, pg_catalog.pg_get_function_identity_arguments(p.oid) as argument_data_types FROM pg_catalog.pg_proc p LEFT JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace JOIN pg_roles on p.proowner = pg_roles.oid WHERE pg_catalog.pg_function_is_visible(p.oid) AND n.nspname = '#{schema}' AND rolname = '#{owner_role}' } execute_query(@conn, query).map do |f| f = f.deep_symbolize_keys Function.new( database_name: @database_name, database_schema: f[:nspname], name: f[:proname], argument_data_types: f[:argument_data_types] ) end end def roles execute_query(@conn, "SELECT usename from pg_user").map do |r| r = r.deep_symbolize_keys Role.new(database_name: @database_name, name: r[:usename]) end end private def execute_query(conn, query) activerecord_connection?(conn) ? conn.select_all(query) : conn[query].all end def activerecord_connection?(conn) ## Right now we have two kind of connections from Sequel and from AR conn.is_a? ActiveRecord::ConnectionAdapters::AbstractAdapter end end end end