// The MIT License // // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. // // Copyright (c) 2020 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package postgresql import ( "database/sql" "go.temporal.io/server/common/persistence/sql/sqlplugin" ) const ( executionsColumns = `shard_id, namespace_id, workflow_id, run_id, next_event_id, last_write_version, data, data_encoding, state, state_encoding` createExecutionQuery = `INSERT INTO executions(` + executionsColumns + `) VALUES(:shard_id, :namespace_id, :workflow_id, :run_id, :next_event_id, :last_write_version, :data, :data_encoding, :state, :state_encoding)` updateExecutionQuery = `UPDATE executions SET next_event_id = :next_event_id, last_write_version = :last_write_version, data = :data, data_encoding = :data_encoding, state = :state, state_encoding = :state_encoding WHERE shard_id = :shard_id AND namespace_id = :namespace_id AND workflow_id = :workflow_id AND run_id = :run_id` getExecutionQuery = `SELECT ` + executionsColumns + ` FROM executions WHERE shard_id = $1 AND namespace_id = $2 AND workflow_id = $3 AND run_id = $4` deleteExecutionQuery = `DELETE FROM executions WHERE shard_id = $1 AND namespace_id = $2 AND workflow_id = $3 AND run_id = $4` lockExecutionQueryBase = `SELECT next_event_id FROM executions WHERE shard_id = $1 AND namespace_id = $2 AND workflow_id = $3 AND run_id = $4` writeLockExecutionQuery = lockExecutionQueryBase + ` FOR UPDATE` readLockExecutionQuery = lockExecutionQueryBase + ` FOR SHARE` createCurrentExecutionQuery = `INSERT INTO current_executions (shard_id, namespace_id, workflow_id, run_id, create_request_id, state, status, start_version, last_write_version) VALUES (:shard_id, :namespace_id, :workflow_id, :run_id, :create_request_id, :state, :status, :start_version, :last_write_version)` deleteCurrentExecutionQuery = "DELETE FROM current_executions WHERE shard_id = $1 AND namespace_id = $2 AND workflow_id = $3 AND run_id = $4" getCurrentExecutionQuery = `SELECT shard_id, namespace_id, workflow_id, run_id, create_request_id, state, status, start_version, last_write_version FROM current_executions WHERE shard_id = $1 AND namespace_id = $2 AND workflow_id = $3` lockCurrentExecutionJoinExecutionsQuery = `SELECT ce.shard_id, ce.namespace_id, ce.workflow_id, ce.run_id, ce.create_request_id, ce.state, ce.status, ce.start_version, e.last_write_version FROM current_executions ce INNER JOIN executions e ON e.shard_id = ce.shard_id AND e.namespace_id = ce.namespace_id AND e.workflow_id = ce.workflow_id AND e.run_id = ce.run_id WHERE ce.shard_id = $1 AND ce.namespace_id = $2 AND ce.workflow_id = $3 FOR UPDATE` lockCurrentExecutionQuery = getCurrentExecutionQuery + ` FOR UPDATE` updateCurrentExecutionsQuery = `UPDATE current_executions SET run_id = :run_id, create_request_id = :create_request_id, state = :state, status = :status, start_version = :start_version, last_write_version = :last_write_version WHERE shard_id = :shard_id AND namespace_id = :namespace_id AND workflow_id = :workflow_id ` createTransferTasksQuery = `INSERT INTO transfer_tasks(shard_id, task_id, data, data_encoding) VALUES(:shard_id, :task_id, :data, :data_encoding)` getTransferTaskQuery = `SELECT task_id, data, data_encoding FROM transfer_tasks WHERE shard_id = $1 AND task_id = $2` getTransferTasksQuery = `SELECT task_id, data, data_encoding FROM transfer_tasks WHERE shard_id = $1 AND task_id > $2 AND task_id <= $3 ORDER BY shard_id, task_id` deleteTransferTaskQuery = `DELETE FROM transfer_tasks WHERE shard_id = $1 AND task_id = $2` rangeDeleteTransferTaskQuery = `DELETE FROM transfer_tasks WHERE shard_id = $1 AND task_id > $2 AND task_id <= $3` createTimerTasksQuery = `INSERT INTO timer_tasks (shard_id, visibility_timestamp, task_id, data, data_encoding) VALUES (:shard_id, :visibility_timestamp, :task_id, :data, :data_encoding)` getTimerTaskQuery = `SELECT visibility_timestamp, task_id, data, data_encoding FROM timer_tasks WHERE shard_id = $1 AND visibility_timestamp = $2 AND task_id = $3` getTimerTasksQuery = `SELECT visibility_timestamp, task_id, data, data_encoding FROM timer_tasks WHERE shard_id = $1 AND ((visibility_timestamp >= $2 AND task_id >= $3) OR visibility_timestamp > $4) AND visibility_timestamp < $5 ORDER BY visibility_timestamp,task_id LIMIT $6` deleteTimerTaskQuery = `DELETE FROM timer_tasks WHERE shard_id = $1 AND visibility_timestamp = $2 AND task_id = $3` rangeDeleteTimerTaskQuery = `DELETE FROM timer_tasks WHERE shard_id = $1 AND visibility_timestamp >= $2 AND visibility_timestamp < $3` createReplicationTasksQuery = `INSERT INTO replication_tasks (shard_id, task_id, data, data_encoding) VALUES(:shard_id, :task_id, :data, :data_encoding)` getReplicationTaskQuery = `SELECT task_id, data, data_encoding FROM replication_tasks WHERE shard_id = $1 AND task_id = $2` getReplicationTasksQuery = `SELECT task_id, data, data_encoding FROM replication_tasks WHERE shard_id = $1 AND task_id > $2 AND task_id <= $3 ORDER BY task_id LIMIT $4` deleteReplicationTaskQuery = `DELETE FROM replication_tasks WHERE shard_id = $1 AND task_id = $2` rangeDeleteReplicationTaskQuery = `DELETE FROM replication_tasks WHERE shard_id = $1 AND task_id > $2 AND task_id <= $3` getReplicationTaskDLQQuery = `SELECT task_id, data, data_encoding FROM replication_tasks_dlq WHERE source_cluster_name = $1 AND shard_id = $2 AND task_id <= $3` getReplicationTasksDLQQuery = `SELECT task_id, data, data_encoding FROM replication_tasks_dlq WHERE source_cluster_name = $1 AND shard_id = $2 AND task_id > $3 AND task_id <= $4 ORDER BY task_id LIMIT $5` bufferedEventsColumns = `shard_id, namespace_id, workflow_id, run_id, data, data_encoding` createBufferedEventsQuery = `INSERT INTO buffered_events(` + bufferedEventsColumns + `) VALUES (:shard_id, :namespace_id, :workflow_id, :run_id, :data, :data_encoding)` deleteBufferedEventsQuery = `DELETE FROM buffered_events WHERE shard_id = $1 AND namespace_id = $2 AND workflow_id = $3 AND run_id = $4` getBufferedEventsQuery = `SELECT data, data_encoding FROM buffered_events WHERE shard_id = $1 AND namespace_id = $2 AND workflow_id = $3 AND run_id = $4` insertReplicationTaskDLQQuery = ` INSERT INTO replication_tasks_dlq (source_cluster_name, shard_id, task_id, data, data_encoding) VALUES (:source_cluster_name, :shard_id, :task_id, :data, :data_encoding) ` deleteReplicationTaskFromDLQQuery = ` DELETE FROM replication_tasks_dlq WHERE source_cluster_name = $1 AND shard_id = $2 AND task_id = $3` rangeDeleteReplicationTaskFromDLQQuery = ` DELETE FROM replication_tasks_dlq WHERE source_cluster_name = $1 AND shard_id = $2 AND task_id > $3 AND task_id <= $4` ) // InsertIntoExecutions inserts a row into executions table func (pdb *db) InsertIntoExecutions(row *sqlplugin.ExecutionsRow) (sql.Result, error) { return pdb.conn.NamedExec(createExecutionQuery, row) } // UpdateExecutions updates a single row in executions table func (pdb *db) UpdateExecutions(row *sqlplugin.ExecutionsRow) (sql.Result, error) { return pdb.conn.NamedExec(updateExecutionQuery, row) } // SelectFromExecutions reads a single row from executions table func (pdb *db) SelectFromExecutions(filter sqlplugin.ExecutionsFilter) (*sqlplugin.ExecutionsRow, error) { var row sqlplugin.ExecutionsRow err := pdb.conn.Get(&row, getExecutionQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID, filter.RunID) if err != nil { return nil, err } return &row, err } // DeleteFromExecutions deletes a single row from executions table func (pdb *db) DeleteFromExecutions(filter sqlplugin.ExecutionsFilter) (sql.Result, error) { return pdb.conn.Exec(deleteExecutionQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID, filter.RunID) } // ReadLockExecutions acquires a write lock on a single row in executions table func (pdb *db) ReadLockExecutions(filter sqlplugin.ExecutionsFilter) (int64, error) { var nextEventID int64 err := pdb.conn.Get(&nextEventID, readLockExecutionQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID, filter.RunID) return nextEventID, err } // WriteLockExecutions acquires a write lock on a single row in executions table func (pdb *db) WriteLockExecutions(filter sqlplugin.ExecutionsFilter) (int64, error) { var nextEventID int64 err := pdb.conn.Get(&nextEventID, writeLockExecutionQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID, filter.RunID) return nextEventID, err } // InsertIntoCurrentExecutions inserts a single row into current_executions table func (pdb *db) InsertIntoCurrentExecutions(row *sqlplugin.CurrentExecutionsRow) (sql.Result, error) { return pdb.conn.NamedExec(createCurrentExecutionQuery, row) } // UpdateCurrentExecutions updates a single row in current_executions table func (pdb *db) UpdateCurrentExecutions(row *sqlplugin.CurrentExecutionsRow) (sql.Result, error) { return pdb.conn.NamedExec(updateCurrentExecutionsQuery, row) } // SelectFromCurrentExecutions reads one or more rows from current_executions table func (pdb *db) SelectFromCurrentExecutions(filter sqlplugin.CurrentExecutionsFilter) (*sqlplugin.CurrentExecutionsRow, error) { var row sqlplugin.CurrentExecutionsRow err := pdb.conn.Get(&row, getCurrentExecutionQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID) return &row, err } // DeleteFromCurrentExecutions deletes a single row in current_executions table func (pdb *db) DeleteFromCurrentExecutions(filter sqlplugin.CurrentExecutionsFilter) (sql.Result, error) { return pdb.conn.Exec(deleteCurrentExecutionQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID, filter.RunID) } // LockCurrentExecutions acquires a write lock on a single row in current_executions table func (pdb *db) LockCurrentExecutions(filter sqlplugin.CurrentExecutionsFilter) (*sqlplugin.CurrentExecutionsRow, error) { var row sqlplugin.CurrentExecutionsRow err := pdb.conn.Get(&row, lockCurrentExecutionQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID) return &row, err } // LockCurrentExecutionsJoinExecutions joins a row in current_executions with executions table and acquires a // write lock on the result func (pdb *db) LockCurrentExecutionsJoinExecutions(filter sqlplugin.CurrentExecutionsFilter) ([]sqlplugin.CurrentExecutionsRow, error) { var rows []sqlplugin.CurrentExecutionsRow err := pdb.conn.Select(&rows, lockCurrentExecutionJoinExecutionsQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID) return rows, err } // InsertIntoTransferTasks inserts one or more rows into transfer_tasks table func (pdb *db) InsertIntoTransferTasks(rows []sqlplugin.TransferTasksRow) (sql.Result, error) { return pdb.conn.NamedExec(createTransferTasksQuery, rows) } // SelectFromTransferTasks reads one or more rows from transfer_tasks table func (pdb *db) SelectFromTransferTasks(filter sqlplugin.TransferTasksFilter) ([]sqlplugin.TransferTasksRow, error) { var rows []sqlplugin.TransferTasksRow err := pdb.conn.Select(&rows, getTransferTaskQuery, filter.ShardID, filter.TaskID) if err != nil { return nil, err } return rows, err } // RangeSelectFromTransferTasks reads one or more rows from transfer_tasks table func (pdb *db) RangeSelectFromTransferTasks(filter sqlplugin.TransferTasksRangeFilter) ([]sqlplugin.TransferTasksRow, error) { var rows []sqlplugin.TransferTasksRow err := pdb.conn.Select(&rows, getTransferTasksQuery, filter.ShardID, filter.MinTaskID, filter.MaxTaskID) if err != nil { return nil, err } return rows, err } // DeleteFromTransferTasks deletes one or more rows from transfer_tasks table func (pdb *db) DeleteFromTransferTasks(filter sqlplugin.TransferTasksFilter) (sql.Result, error) { return pdb.conn.Exec(deleteTransferTaskQuery, filter.ShardID, filter.TaskID) } // RangeDeleteFromTransferTasks deletes one or more rows from transfer_tasks table func (pdb *db) RangeDeleteFromTransferTasks(filter sqlplugin.TransferTasksRangeFilter) (sql.Result, error) { return pdb.conn.Exec(rangeDeleteTransferTaskQuery, filter.ShardID, filter.MinTaskID, filter.MaxTaskID) } // InsertIntoTimerTasks inserts one or more rows into timer_tasks table func (pdb *db) InsertIntoTimerTasks(rows []sqlplugin.TimerTasksRow) (sql.Result, error) { for i := range rows { rows[i].VisibilityTimestamp = pdb.converter.ToPostgreSQLDateTime(rows[i].VisibilityTimestamp) } return pdb.conn.NamedExec(createTimerTasksQuery, rows) } // SelectFromTimerTasks reads one or more rows from timer_tasks table func (pdb *db) SelectFromTimerTasks(filter sqlplugin.TimerTasksFilter) ([]sqlplugin.TimerTasksRow, error) { var rows []sqlplugin.TimerTasksRow filter.VisibilityTimestamp = pdb.converter.ToPostgreSQLDateTime(filter.VisibilityTimestamp) err := pdb.conn.Select(&rows, getTimerTaskQuery, filter.ShardID, filter.VisibilityTimestamp, filter.TaskID) if err != nil { return nil, err } for i := range rows { rows[i].VisibilityTimestamp = pdb.converter.FromPostgreSQLDateTime(rows[i].VisibilityTimestamp) } return rows, err } // RangeSelectFromTimerTasks reads one or more rows from timer_tasks table func (pdb *db) RangeSelectFromTimerTasks(filter sqlplugin.TimerTasksRangeFilter) ([]sqlplugin.TimerTasksRow, error) { var rows []sqlplugin.TimerTasksRow filter.MinVisibilityTimestamp = pdb.converter.ToPostgreSQLDateTime(filter.MinVisibilityTimestamp) filter.MaxVisibilityTimestamp = pdb.converter.ToPostgreSQLDateTime(filter.MaxVisibilityTimestamp) err := pdb.conn.Select(&rows, getTimerTasksQuery, filter.ShardID, filter.MinVisibilityTimestamp, filter.TaskID, filter.MinVisibilityTimestamp, filter.MaxVisibilityTimestamp, filter.PageSize) if err != nil { return nil, err } for i := range rows { rows[i].VisibilityTimestamp = pdb.converter.FromPostgreSQLDateTime(rows[i].VisibilityTimestamp) } return rows, err } // DeleteFromTimerTasks deletes one or more rows from timer_tasks table func (pdb *db) DeleteFromTimerTasks(filter sqlplugin.TimerTasksFilter) (sql.Result, error) { filter.VisibilityTimestamp = pdb.converter.ToPostgreSQLDateTime(filter.VisibilityTimestamp) return pdb.conn.Exec(deleteTimerTaskQuery, filter.ShardID, filter.VisibilityTimestamp, filter.TaskID) } // RangeDeleteFromTimerTasks deletes one or more rows from timer_tasks table func (pdb *db) RangeDeleteFromTimerTasks(filter sqlplugin.TimerTasksRangeFilter) (sql.Result, error) { filter.MinVisibilityTimestamp = pdb.converter.ToPostgreSQLDateTime(filter.MinVisibilityTimestamp) filter.MaxVisibilityTimestamp = pdb.converter.ToPostgreSQLDateTime(filter.MaxVisibilityTimestamp) return pdb.conn.Exec(rangeDeleteTimerTaskQuery, filter.ShardID, filter.MinVisibilityTimestamp, filter.MaxVisibilityTimestamp) } // InsertIntoBufferedEvents inserts one or more rows into buffered_events table func (pdb *db) InsertIntoBufferedEvents(rows []sqlplugin.BufferedEventsRow) (sql.Result, error) { return pdb.conn.NamedExec(createBufferedEventsQuery, rows) } // SelectFromBufferedEvents reads one or more rows from buffered_events table func (pdb *db) SelectFromBufferedEvents(filter *sqlplugin.BufferedEventsFilter) ([]sqlplugin.BufferedEventsRow, error) { var rows []sqlplugin.BufferedEventsRow err := pdb.conn.Select(&rows, getBufferedEventsQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID, filter.RunID) for i := 0; i < len(rows); i++ { rows[i].NamespaceID = filter.NamespaceID rows[i].WorkflowID = filter.WorkflowID rows[i].RunID = filter.RunID rows[i].ShardID = filter.ShardID } return rows, err } // DeleteFromBufferedEvents deletes one or more rows from buffered_events table func (pdb *db) DeleteFromBufferedEvents(filter *sqlplugin.BufferedEventsFilter) (sql.Result, error) { return pdb.conn.Exec(deleteBufferedEventsQuery, filter.ShardID, filter.NamespaceID, filter.WorkflowID, filter.RunID) } // InsertIntoReplicationTasks inserts one or more rows into replication_tasks table func (pdb *db) InsertIntoReplicationTasks(rows []sqlplugin.ReplicationTasksRow) (sql.Result, error) { return pdb.conn.NamedExec(createReplicationTasksQuery, rows) } // SelectFromReplicationTasks reads one or more rows from replication_tasks table func (pdb *db) SelectFromReplicationTasks(filter sqlplugin.ReplicationTasksFilter) ([]sqlplugin.ReplicationTasksRow, error) { var rows []sqlplugin.ReplicationTasksRow err := pdb.conn.Select(&rows, getReplicationTaskQuery, filter.ShardID, filter.TaskID) return rows, err } // RangeSelectFromReplicationTasks reads one or more rows from replication_tasks table func (pdb *db) RangeSelectFromReplicationTasks(filter sqlplugin.ReplicationTasksRangeFilter) ([]sqlplugin.ReplicationTasksRow, error) { var rows []sqlplugin.ReplicationTasksRow err := pdb.conn.Select(&rows, getReplicationTasksQuery, filter.ShardID, filter.MinTaskID, filter.MaxTaskID, filter.PageSize) return rows, err } // DeleteFromReplicationTasks deletes one rows from replication_tasks table func (pdb *db) DeleteFromReplicationTasks(filter sqlplugin.ReplicationTasksFilter) (sql.Result, error) { return pdb.conn.Exec(deleteReplicationTaskQuery, filter.ShardID, filter.TaskID) } // RangeDeleteFromReplicationTasks deletes multi rows from replication_tasks table func (pdb *db) RangeDeleteFromReplicationTasks(filter sqlplugin.ReplicationTasksRangeFilter) (sql.Result, error) { return pdb.conn.Exec(rangeDeleteReplicationTaskQuery, filter.ShardID, filter.MinTaskID, filter.MaxTaskID) } // InsertIntoReplicationTasksDLQ inserts one or more rows into replication_tasks_dlq table func (pdb *db) InsertIntoReplicationDLQTasks(rows []sqlplugin.ReplicationDLQTasksRow) (sql.Result, error) { return pdb.conn.NamedExec(insertReplicationTaskDLQQuery, rows) } // SelectFromReplicationTasksDLQ reads one or more rows from replication_tasks_dlq table func (pdb *db) SelectFromReplicationDLQTasks(filter sqlplugin.ReplicationDLQTasksFilter) ([]sqlplugin.ReplicationDLQTasksRow, error) { var rows []sqlplugin.ReplicationDLQTasksRow err := pdb.conn.Select( &rows, getReplicationTaskDLQQuery, filter.SourceClusterName, filter.ShardID, filter.TaskID, ) return rows, err } // RangeSelectFromReplicationTasksDLQ reads one or more rows from replication_tasks_dlq table func (pdb *db) RangeSelectFromReplicationDLQTasks(filter sqlplugin.ReplicationDLQTasksRangeFilter) ([]sqlplugin.ReplicationDLQTasksRow, error) { var rows []sqlplugin.ReplicationDLQTasksRow err := pdb.conn.Select( &rows, getReplicationTasksDLQQuery, filter.SourceClusterName, filter.ShardID, filter.MinTaskID, filter.MaxTaskID, filter.PageSize, ) return rows, err } // DeleteMessageFromReplicationTasksDLQ deletes one row from replication_tasks_dlq table func (pdb *db) DeleteFromReplicationDLQTasks( filter sqlplugin.ReplicationDLQTasksFilter, ) (sql.Result, error) { return pdb.conn.Exec( deleteReplicationTaskFromDLQQuery, filter.SourceClusterName, filter.ShardID, filter.TaskID, ) } // DeleteMessageFromReplicationTasksDLQ deletes one or more rows from replication_tasks_dlq table func (pdb *db) RangeDeleteFromReplicationDLQTasks( filter sqlplugin.ReplicationDLQTasksRangeFilter, ) (sql.Result, error) { return pdb.conn.Exec( rangeDeleteReplicationTaskFromDLQQuery, filter.SourceClusterName, filter.ShardID, filter.MinTaskID, filter.MaxTaskID, ) }