module Prick module Build class BuildBatch include Timer attr_reader :builder forward_to :builder, :conn def kind() @nodes.first&.kind end attr_reader :nodes def initialize(builder) @builder = builder @nodes = [] end def execute(&block) name = self.class.to_s.sub(/.*::/, "").split(/(?=[A-Z])/).map(&:downcase).join(" ") time "Execute #{name} (nodes: #{nodes.size})" do yield end end def dump puts kind.upcase indent { nodes.each(&:dump) } end end class SqlBatch < BuildBatch def execute super { begin sql = [] if nodes.first.is_a?(ExeNode) time "Execute script" do sql = [nodes.first.source] end end conn.execute sql + nodes[sql.size..-1].map(&:source) rescue PG::SyntaxError, PG::Error => ex error, line, pos = parse_pg_message(ex.message) if line if line < nodes.first.lines node = nodes.first; else line -= nodes.first.lines node = nodes[1..-1].find { |node| line -= 1 if line < node.lines line -= 2 true else line -= node.lines false end } end if node.is_a?(SqlNode) message = ["prick: #{error} in #{node.path}", line, pos].compact.join(":") else message = "prick: #{error} from #{node.path}" end else raise end raise PostgresError.new(message) end } end private # Returns [error, line, pos] tuple def parse_pg_message(message) message =~ /ERROR:\s*(.*?)\n(LINE (\d+): ).*?\n(\s*\^)/m or return nil [$1, $3.to_i, $4.size - $2.size] end end class ModuleBatch < BuildBatch def execute super { nodes.each { |node| sql = node&.call conn.execute sql if sql } } end end class FoxBatch < BuildBatch # Inlining Fox instead of using command line program as it saves some deciseconds def execute t_type = t_fox = t_exe = nil super { timer = Timer.new "Execute fox batch (nodes: #{nodes.size}, clean: #{builder.clean})" t_type = Timer.new " Load data model" files = nodes.map(&:path) # Load meta object meta = PgMeta.new(conn, exclude_schemas: builder.pg_graph_ignore_schemas) # Create type object type = PgGraph::Type.new(meta, builder.reflections_file) # Timer t_type.stop t_fox = Timer.new " Parse files" # Create fox object fox = FixtureFox::Fox.new(type) # Parse files for file in files source, lines = fox.tokenize(file) fox.parse(source, lines) end # Analyze fox.assign_types fox.generate # Dump state file fox.write_state(FOX_STATE_PATH) # Timer t_fox.stop t_exe = Timer.new " Execute SQL" delete = builder.clean ? :none : :touched begin conn.execute fox.to_sql(format: :exec, delete: delete) # FIXME: Why only in FoxBatch - should be set higher up in the system conn.execute "update prick.versions set built_at = now() at time zone 'UTC'" rescue PG::SyntaxError, PG::Error => ex raise PostgresError, "prick: #{ex.message}" end } t_type.emit t_fox.emit t_exe.emit end end end end