repo

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 16, 2024 License: MPL-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package repo specifies the expected interfaces for management of repositories, including a database connections pool which can be used concurrently by several goroutines, how individual connections and transactions may be obtained from it, which repositories exist, and which actions may be performed on them.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cars

type Cars interface {
	// Conn takes a Conn interface instance, unwraps it as required,
	// and returns a CarsConnQueryer interface which (with access to
	// the implementation-dependent connection object) can run different
	// permitted operations on cars.
	Conn(Conn) CarsConnQueryer

	// Tx takes a Tx interface instance, unwraps it as required,
	// and returns a CarsTxQueryer interface which (with access to the
	// implementation-dependent transaction object) can run different
	// permitted operations on cars.
	Tx(Tx) CarsTxQueryer
}

Cars interface represents an example repository for management of the car instances. A repository interface should provide two methods of Conn and Tx in order to encourage developer to explicitly decide that a connection or a transaction is required for execution of a SQL statement. Each of those two methods will take a Conn/Tx interface which was provided by the repository implementation (from the adapter layer) beforehand. Implementation of these Conn()/Tx() methods may safely unwrap these interfaces and access the underlying structs if needed, hence, the unwrapping is performed just once.

type CarsConnQueryer

type CarsConnQueryer interface {
	CarsQueryer
}

CarsConnQueryer interface lists all operations which may be executed in a Cars repository having an open connection with auto-committed transactions. Those operations which must be executed in a connection (and may not be executed in an ongoing transaction which may keep running other statements after this one) must be listed here, while other operations which do not strictly require an open connection (and may use an open transaction too) must be defined in the embedded CarsQueryer interface. This design allows a unified implementation, while forcing developers to think about the consequences of having one or multiple transactions.

type CarsQueryer

type CarsQueryer interface {
	// UnparkAndMove example operation unparks a car with carID UUID,
	// and moves it to the c destination coordinate. Updated car model
	// and possible errors are returned.
	UnparkAndMove(ctx context.Context, carID uuid.UUID, c model.Coordinate) (*model.Car, error)

	// Park example operation parks the car with carID UUID without
	// changing its current location. It returns the updated car model
	// and possible errors.
	// The parking mode is recorded too.
	Park(ctx context.Context, carID uuid.UUID, mode model.ParkingMode) (*model.Car, error)
}

CarsQueryer interface lists common operations which may be executed in a Cars repository having either a connection or transaction at hand. This interface is embedded by both of CarsConnQueryer and CarsTxQueryer in order to avoid redundant implementation.

type CarsTxQueryer

type CarsTxQueryer interface {
	CarsQueryer
}

CarsTxQueryer interface lists all operations which may be executed in a Cars repository having an ongoing transaction. Those operations which must be executed in a transaction (and may not be executed with a connection) must be listed here, while other operations which do not strictly require an open transaction (and can use their own auto-committed transaction too) must be defined in the embedded CarsQueryer interface. This design allows a unified implementation, while forcing developers to think about the consequences of having one or multiple transactions.

type Conn

type Conn interface {
	Queryer

	// Tx begins a new transaction in this connection, calls the handler
	// with the ctx (which was used for beginning the transactions) and
	// the fresh transaction, and commits the transaction ultimately.
	// In case of errors, the transaction will be rolled back and the
	// error will be returned too.
	Tx(ctx context.Context, handler TxHandler) error

	// IsConn method prevents a non-Conn object to mistakenly implement
	// the Conn interface.
	IsConn()
}

Conn represents a database connection. It is unsafe to be used concurrently. A connection may be used in order to execute one or more SQL statements or start transactions one at a time. For statement execution methods, see the Queryer interface.

type ConnHandler

type ConnHandler func(context.Context, Conn) error

ConnHandler is a handler function which takes a context and a database connection which should be used solely from the current goroutine (or by proper synchronization). When it returns, the connection may be released and reused by other routines.

type DownMigrator added in v1.1.0

type DownMigrator[S any] interface {
	Settler[S]

	// MigrateDown creates the previous/downer version of the migrating
	// resource either physically (e.g., by creating a new settings
	// struct instance in order to maintain settings items in memory)
	// or logically (e.g., by creating some views in the downer version
	// format based on queries over views or tables from the current
	// version in order to proceed with a database schema migration).
	// Finally, another DownMigrator[S] instance will be created and
	// returned which describes the migrating resource at its downer
	// version, so the migration may continue.
	// If there is no more downer major version, an error will be
	// returned.
	MigrateDown(ctx context.Context) (DownMigrator[S], error)
}

DownMigrator of S interface specifies the downwards migrator objects requirements for a resource with S settler type. It embeds the Settler[S] interface and has one main method, the MigrateDown method. The S type parameter which represents the migration settler type can be filled, for example, by pkg/core/usecase/migrationuc.Settings for configuration settings migration or the SchemaSettler interface from this package for database schema migration. The MigrateDown method may be used to migrate one major version downwards and obtain the previous relevant DownMigrator[S] instance, while the Settler method from the Settler[S] interface may be used to obtain an instance of S in order to settle the migration operation at its ultimate major version. The actual logic of migration settlement depends on the S type and is not defined by DownMigrator[S] itself.

type Migrator added in v1.1.0

type Migrator[S any] interface {
	// Settler creates and returns a settler object (with S type) which
	// can be used to settle the migration results.
	// For example, a configuration settler object may be used for
	// merging the default configuration settings or a database schema
	// settler object may be used in order to persist tables in a local
	// schema. This method is a shortcut when the source and destination
	// major versions are the same. It may migrate from the current
	// minor version to the latest supported minor version before
	// creating the settler object, hence, it may return an error
	// despite the Settler[S] interface which is embedded by the
	// UpMigrator[S] and the DownMigrator[S] interfaces and may not
	// return an error.
	Settler(ctx context.Context) (S, error)

	// Load tries to load the source version of the migrating resource
	// and return an error if it failed to load it completely.
	// The loading may be performed by creating an in-memory
	// representation of that resource in simple scenarios such as
	// loading a configuration file or may be performed by setting up
	// some metadata alone in order to make the migrating resource
	// accessible, such as a FDW provided schema for migrating a DB.
	// All extra information which may be required for finding out
	// the source version of the migrating resource should be kept by
	// the Migrator[S] instance since its instantiation time.
	// Calling Load method multiple times causes undefined behavior and
	// an implementation is free to ignore extra calls, load data again,
	// or return an error.
	Load(ctx context.Context) error

	// MajorVersion returns the major semantic version of this Migrator
	// instance. It may reflect the major version of a configuration
	// file or a database schema for example. This method may be used
	// for identification of the migration versions path, passing
	// through the major versions one by one.
	MajorVersion() uint

	// UpMigrator creates a new upwards migrator object. It may use a
	// version-specific struct for its implementation, however, it has
	// to adapt the created struct to the version-independent UpMigrator
	// interface, so it can be used uniformly in the use cases layer.
	// This upwards migrator may contain extra information such as a
	// loaded configuration settings struct or an open database
	// transaction as appropriate. Such information are transparent at
	// the interface level.
	//
	// Before calling UpMigrator, it is necessary to call Load method
	// in order to obtain a resource at its source major and minor
	// version (or at a more recent minor version in simplest cases).
	// After calling UpMigrator, the resource will be migrated to the
	// latest supported minor version within the source major version.
	// Obtained UpMigrator[S] instance may be used to keep migrating
	// to the next major versions (and their latest supported minor
	// versions) one at a time.
	UpMigrator(ctx context.Context) (UpMigrator[S], error)

	// DownMigrator creates a new downwards migrator object. It may use
	// a version-specific struct for its implementation, however, it has
	// to adapt the created struct to the version-independent
	// DownMigrator interface, so it can be used uniformly in the
	// use cases layer. This downwards migrator may contain extra
	// information such as a loaded configuration settings struct or
	// an open database transaction as appropriate. Such information
	// are transparent at the interface level.
	//
	// Before calling DownMigrator, it is necessary to call Load method
	// in order to obtain a resource at its source major and minor
	// version (or at a more recent minor version in simplest cases).
	// After calling DownMigrator, the resource will be migrated to the
	// latest supported minor version within the source major version.
	// Obtained DownMigrator[S] instance may be used to keep migrating
	// to the previous major versions (and their latest supported minor
	// versions) one at a time.
	DownMigrator(ctx context.Context) (DownMigrator[S], error)
}

Migrator of S is the core migration interface which defines how a semantically versioned resource may be migrated from a S1.S2.S3 source version to a D1.D2.D3 destination version. The S type parameter represents the migration settler type of the versioned resource. For example, pkg/core/usecase/migrationuc.Settings interface can be used as S parameter for configuration settings migration and the SchemaSettler interface can be used as S for DB schema migration.

Migration of a given resource consists of three main phases.

  1. Source version of the given resource should be loaded. For simple resources which can be held completely in memory, such as config settings, this step can load and hold the source version resource as an struct instance in memory. While for more complex resources such as a DB schema, it may use a transaction in the target DB in order to connect to and load the source database schema as a Foreign Data Wrapper (FDW) without actually transferring data items. Relevant extra information, such as an open transaction in the destination database or source database connection URL or a config file data may be fetched all while instantiating the Migrator[S]. The Load function is responsible for this phase.
  2. Comparing the source and destination semantic versions indicates that an upwards or downwards migration is required. If those versions are equal, either direction may be used. The UpMigrator and DownMigrator methods create an upwards and downwards migrator object with UpMigrator[S] and DownMigrator[S] types respectively. For simple scenarios such as configuration files where all minor versions of each major version can be loaded similarly, these methods just have to create a migrator object. However, for more complex scenarios such as DB schema migration where each minor version requires a distinct schema definition and loading logic, the UpMigrator and DownMigrator have to migrate the source version to its latest supported minor version (in the same major version) too. Thereafter, migrator objects can be used to create new resources at the next/previous version moving over one major version at a time. A DB schema migration implementation may create new database views based on the views or tables which were defined for the previous/next version, so they can complete without transferring actual data rows. More complex changes may mandate a migrator object to create a table and materialize its changes or even load data rows in the Golang process memory. Such details will remain transparent at interface level.
  3. After migrating a resource upwards/downwards over its major version and obtaining a migrator object with the destination major version (and its latest supported minor and patch versions) using the UpMigrator[S] or DownMigrator[S] instances, the Settler[S] interface (which is embedded by both of them) can be used to obtain the migration settler instance. For simple resources like configuration settings, the settler instance is used to marshal and save the ultimate resource (or merge it with the target settings version default values). For more complex resources like schema migration which had proceeded the migration without actually fetching and converting data items (but just kept some metadata about them by creating a series of views), the settler instance may be used to persist the migration results by creating the ultimate resources (e.g., tables which their rows are obtained by querying the previous/next version views). For more details, check the SchemaSettler interface.

By the way, Migrator[S] provides a Settler method, so the special scenario which has the same source and destination major version may be simplified and the settler instance can be fetched directly instead of taking an upwards/downwards migrator object and using it to fetch the settler instance afterwards, although, an implementation is free to employ this sequence of method calls (in order to realize the Settler[S] requirements). This Settler method, despite the generic Settler[S] interface, may return an error too because this Settler method may need to migrate from the current minor version to the latest supported minor version (as expected by the database schema migration settlers) and so may fail. However, the Settler[S] only has to create a settler object without attempting any real migration (which is the duty of the returned settler object itself).

type Pool

type Pool interface {
	// Conn acquires a database connection, passes it into the
	// handler function, and when it returns will release the connection
	// so it may be used by other callers.
	// This method may be blocked (as while as the ctx allows it)
	// until a connection is obtained. That connection will not be
	// used by any other handler concurrently.
	// Returned errors from the handler will be returned by this
	// method after possible wrapping.
	// The ctx which is used for acquisition of a connection is also
	// passed to the handler function.
	Conn(ctx context.Context, handler ConnHandler) error

	// Close closes all connections of this connection pool and returns
	// any occurred error.
	Close() error
}

Pool represents a database connection pool. It may be used concurrently from different goroutines.

type Queryer

type Queryer interface {
	// Exec runs SQL statements with given args given ctx context.
	// Number of affected rows and possible errors will be returned.
	// If args is provided, sql will be prepared and args will be passed
	// separately to the DBMS in order to prevent SQL injection.
	// In this case, sql must contain exactly one statement.
	// In absence of args, sql may contain multiple semi-colon separated
	// statements too.
	//
	// Parameters in sql should be numbered like $1, $2, etc. as they
	// are supported by the PostgreSQL wire protocol natively.
	// This should not be confused with ? or @name parameters which are
	// supported by some ORM libraries via query rewriting. Those extra
	// formats may be supported by an implementation, however, users of
	// this interface should solely use the native numbered placeholders
	// in order to stay independent of the adapter/frameworks layers.
	Exec(ctx context.Context, sql string, args ...any) (count int64, err error)

	// Query runs SQL statement with given args given ctx context.
	// The result set is returned as the Rows interface, while errors
	// are returned as the second return value (if any).
	// If args is provided, sql will be prepared and args will be passed
	// separately to the DBMS in order to prevent SQL injection.
	// Nevertheless, sql must contain exactly one statement.
	//
	// Parameters in sql should be numbered like $1, $2, etc. as they
	// are supported by the PostgreSQL wire protocol natively.
	// This should not be confused with ? or @name parameters which are
	// supported by some ORM libraries via query rewriting. Those extra
	// formats may be supported by an implementation, however, users of
	// this interface should solely use the native numbered placeholders
	// in order to stay independent of the adapter/frameworks layers.
	//
	// The Query or Exec may not be called again until the Rows is
	// closed since only one ongoing statement may be used on each
	// connection. If you need to run multiple queries concurrently,
	// either use multiple connections or rewrite the query using
	// the CURSOR concept:
	// https://www.postgresql.org/docs/current/plpgsql-cursors.html
	Query(ctx context.Context, sql string, args ...any) (Rows, error)
}

Queryer interface includes methods for running SQL statements. There are two main types of statements. One category may affect multiple rows, but do not return a result set, like DDL commands or an UPDATE without a RETURNING clause. These statements are executed with the Exec method. Another category of statements (which may or may not modify the database contents) provide a result set, like SELECT or an UPDATE with a RETURNING clause. These queries may be executed with the Query method. This interface is embedded by both of Conn and Tx since they may be used for execution of commands, of course, with distinct isolation levels.

type Role added in v1.1.0

type Role string

Role is a string specifying a database connection role. Each role has a set of granted privileges which indicates which operations may be performed after using it for connecting to a database.

The pkg/core/usecase/migrationuc.SchemaSettings.ConnectionPool needs one Role instance in order to connect to a database (which its identification information are captured from a config file and its authentication information are read from a passwords file).

const (
	// AdminRole is an administrator (super user) role which may be used
	// for creation of other roles, granting them relevant privileges,
	// or creation of empty schema. Generally, the minimal set of
	// operations which are not required normally but may be essential
	// to start other normal use cases, may be performed by this role.
	AdminRole Role = "admin"

	// NormalRole is a normal (unprivilged) role which is used for
	// all common operations, including creation of tables in existing
	// schema and filling them during the migration use cases and also
	// database changes (for the latest schema version only) during
	// the non-migration use cases.
	NormalRole Role = "caweb"
)

These constants specify the expected database roles. At least the AdminRole must exist beforehand (i.e., must be created manually) and it must have super user privileges, so it can be used to create other required roles (if they are not already created). The authentication information of these roles are kept in pass files as indicated in the configuration file.

type Rows

type Rows interface {
	// Close closes the result set, allowing the next query to be
	// executed. After calling this method, the Err() method may be
	// called in order to find out about possible errors.
	Close()

	// Err returns any error which was seen during the last operation.
	// It has to be checked after calling the Close() method.
	Err() error

	// Next prepares the next row to be read using the Scan or Values
	// methods. It must be called even before reading the first row.
	// If it returns false, no more rows exists. In this case, the
	// result set is closed automatically and it is not required to
	// call the Close() method. Nevertheless, calling the Close() is
	// harmless and the Err() method must be checked yet.
	Next() bool

	// Scan reads the values of all columns from the current row
	// into the given dest arguments.
	//
	// Scan converts columns read from the database into the following
	// common Go types and special types provided by the sql package:
	//
	//	*string
	//	*[]byte
	//	*int, *int8, *int16, *int32, *int64
	//	*uint, *uint8, *uint16, *uint32, *uint64
	//	*bool
	//	*float32, *float64
	//	*interface{}
	//	*RawBytes
	//	*Rows (cursor value)
	//	any type implementing Scanner (see Scanner docs)
	Scan(dest ...any) error

	// Values is like Scan, but does not need the caller to prepare
	// destination arguments with relevant types. Instead, it uses
	// *interface{} types in order to read all column values.
	// It is recommended to use the Scan in order to eliminate the
	// subsequent type checking codes.
	Values() ([]any, error)
}

Rows represents the result set of an executed query. Before trying to read a row out of the Rows instance, the Next() must be called. As while as the Next() returns true, a row is obtained from the DBMS server (multiple rows may be fetched and cached based on the implementation details). The Scan or Values methods may be used in order to read columns of the current row. Calling the Close() method releases the result set and ignores any remaining unread rows. The Err() method must be called after the Close() in order to obtain possible errors. Before calling the Close, Err may return nil even if there are some errors since they may not be detected necessarily beforehand.

type Schema added in v1.1.0

type Schema interface {
	// Conn takes a Conn interface instance, unwraps it as required,
	// and returns a SchemaConnQueryer interface which (with access to
	// the implementation-dependent connection object) can create or
	// drop schema or manage database roles.
	Conn(Conn) SchemaConnQueryer

	// Tx takes a Tx interface instance, unwraps it as required,
	// and returns a SchemaTxQueryer interface which (with access to the
	// implementation-dependent transaction object) can manage database
	// roles, change their passwords, or perform schema-level management
	// operations.
	Tx(Tx) SchemaTxQueryer
}

Schema interface presents expectations from a repository which allows database schema and roles management. This repository creates schema and grant relevant privileges on them, so they may be filled by tables during a migration or queried during other use cases.

type SchemaConnQueryer added in v1.1.0

type SchemaConnQueryer interface {
	SchemaQueryer

	// InstallFDWExtensionIfMissing creates the postgres_fdw extension
	// assuming that its relavant .so files are available in proper
	// paths. If the extension is already created, calling this method
	// causes no change.
	InstallFDWExtensionIfMissing(ctx context.Context) error

	// DropServerIfExists drops the `serverName` foreign server, if it
	// exists, with cascade. That is, dependent objects such as its
	// user mapping will be dropped too.
	//
	// Caller is responsible to pass a trusted serverName string.
	DropServerIfExists(ctx context.Context, serverName string) error
}

SchemaConnQueryer interface lists all operations which may be taken with regards to database schema having an open connection with the auto-committed transactions. Those operations which must be executed in a connection (and may not be executed in an ongoing transaction which may keep running other statements after this one) must be listed here, while other operations which do not strictly require an open connection (and may use an open transaction too) must be defined in the embedded SchemaQueryer interface. This design allows a unified implementation, while forcing developers to think about the consequences of having one or multiple transactions.

type SchemaInitializer added in v1.1.0

type SchemaInitializer interface {
	// InitDevSchema creates tables in an existing database schema
	// and fills them with the development suitable initial data.
	// The database connection, target schema name, tables format, and
	// their semantic version are known since the SchemaInitializer
	// interface instantiation time.
	InitDevSchema(ctx context.Context) error

	// InitProdSchema creates tables in an existing database schema
	// and fills them with the production suitable initial data.
	// The database connection, target schema name, tables format, and
	// their semantic version are known since the SchemaInitializer
	// interface instantiation time.
	InitProdSchema(ctx context.Context) error
}

SchemaInitializer interface is exposed by each schema version implementation. It provides two methods of InitDevSchema and InitProdSchema in order to create new tables and fill an existing schema with them, using the development and production suitable initial data rows respectively. Each implementation (for settlement of the latest minor version of a specific major version) should contain the relevant information for finding the destination database (such as a database transaction) so the SchemaInitializer does not need to take any argument.

type SchemaQueryer added in v1.1.0

type SchemaQueryer interface {
	// DropIfExists drops the `schema` schema without cascading if it
	// exists. That is, if `schema` does not exist, a nil error will be
	// returned without any change. And if `schema` exists and is empty,
	// it will be dropped. But if `schema` exists and is not empty, an
	// error will be returned.
	//
	// Caller is responsible to pass a trusted schema name string.
	DropIfExists(ctx context.Context, schema string) error

	// DropCascade drops `schema` schema with cascading, dropping all
	// dependent objects recursively. The `schema` must exist,
	// otherwise, an error will be returned.
	// This method is useful for dropping the intermediate schema
	// which are created during a migration.
	//
	// Caller is responsible to pass a trusted schema name string.
	DropCascade(ctx context.Context, schema string) error

	// CreateSchema tries to create the `schema` schema.
	// There must be no other schema with the `schema` name, otherwise,
	// this operation will fail.
	//
	// Caller is responsible to pass a trusted schema name string.
	CreateSchema(ctx context.Context, schema string) error

	// CreateRoleIfNotExists creates the `role` role if it does not
	// exist right now. Although the login option is enabled for the
	// created role, but no specific password will be set for it.
	// The ChangePasswords method may be used for setting a password if
	// desired. Otherwise, that user may not login effectively (but
	// using the trust or local identity methods).
	//
	// The `role` role name may be suffixed automatically based on
	// this schema queryer settings.
	CreateRoleIfNotExists(ctx context.Context, role Role) error

	// GrantPrivileges grants ALL privileges on the `schema` schema
	// to the `role` role, so it may create or access tables in that
	// schema and run relevant queries.
	//
	// The `role` role name may be suffixed automatically based on
	// this schema queryer settings.
	GrantPrivileges(ctx context.Context, schema string, role Role) error

	// SetSearchPath alters the given database role and sets its default
	// search_path to the given schema name alone.
	//
	// Updated search_path will be used by default in all future
	// transactions by that role, but it may be changed using the SET
	// statement as needed.
	SetSearchPath(ctx context.Context, schema string, role Role) error

	// GrantFDWUsage grants the USAGE privilege on the postgres_fdw
	// extension to the `role` role. Thereafter, that `role` role can
	// use the postgres_fdw extension in order to create a foreign
	// server or create a user mapping for it.
	//
	// The `role` role name may be suffixed automatically based on
	// this schema queryer settings.
	GrantFDWUsage(ctx context.Context, role Role) error
}

SchemaQueryer interface lists common operations which may be taken with regards to database schema having either a connection or open transaction at hand. This interface is embedded by both of the SchemaConnQueryer and the SchemaTxQueryer in order to avoid redundant implementation.

type SchemaSettler added in v1.1.0

type SchemaSettler interface {
	// SettleSchema settles the database schema migration operation.
	//
	// If the migration operation was implemented by creating a set of
	// views, the settlement can be performed by creating the expected
	// database tables and filling them by querying their corresponding
	// views.
	// In absence of errors, settlement persists the migration operation
	// results logically. However, if the entire migration was performed
	// in a transaction, caller is responsible to commit that
	// transaction (not the SchemaSettler interface).
	SettleSchema(ctx context.Context) error

	// MajorVersion returns the major semantic version of this schema
	// settler instance. Each schema settler supports exactly one major
	// version which is also included in its corresponding schema name.
	// For example, caweb1 schema name may be filled by major version 1
	// schema settler.
	MajorVersion() uint
}

SchemaSettler interface specifies the expectations from the settler objects for a database schema migration operation.

An efficient implementation of database migration may use views in order to convert data format without copying data items themselves as far as possible. For this purpose, migration may begin by creating a Foreign Data Wrapper (FDW) link to the source database from within a transaction of the destination database. This link allows source tables to be accessible like a local database schema within the destination database. For migrating to the latest minor version from the same major version, new views may be created in another schema by queries over the FDW imported schema's foreign tables. Thereafter, views may be migrated to one upper/downer version by creating a set of other views in another schema again and again, changing column names and/or computing them based on other views. If some conversions are more complex, they may need to create intermediate tables or even need to load data and process them in the Golang process. Finally, views (or tables) in an ultimate local schema must be materialized into a series of persistent tables in the expected schema. And this is the point that SchemaSettler interface becomes relevant. The SettleSchema method is supposed to perform this final database schema settlement operation.

type SchemaTxQueryer added in v1.1.0

type SchemaTxQueryer interface {
	SchemaQueryer

	// ChangePasswords updates the passwords of the given roles
	// in the current transaction. The roles and passwords slices must
	// have the same number of entries, so they can be used in pair.
	// These fields are not combined as a struct with two role and
	// password fields because passing items separately ensures that
	// all items are initialized explicitly in constrast to a struct
	// which its fields can be zero-initialized and are more suitable
	// to pass a set of optional fields.
	// The given roles may be suffixed automatically too, based on
	// this transaction queryer settings.
	ChangePasswords(
		ctx context.Context, roles []Role, passwords []string,
	) error
}

SchemaTxQueryer interface lists all operations which may be taken with regards to database schema having an ongoing transaction. Those operations which must be executed in a transaction (and may not be executed with a connection) must be listed here, while other operations which do not strictly require an open transaction (and can use their own auto-committed transaction too) must be defined in the embedded SchemaQueryer interface. This design allows a unified implementation, while forcing developers to think about the consequences of having one or multiple transactions.

type Settler added in v1.1.0

type Settler[S any] interface {
	// Settler method returns an instance of S type which represents
	// the migration settler instance. This method should be called
	// after reaching to the ultimate major version. The S instance
	// may be used to finalize migration operation and persist its
	// results.
	Settler() S
}

Settler of S interface specifies the migration settler objects requirements for a resource with S settler type. This interface has one method, namely Settler, and it returns an instance of S type, so it may be used to settle the migration operation. It should be called when the migrating resource has reached to its destination major version and needs to persist its migration results. The S type parameter which represents the migration settler type can be filled, for example, by pkg/core/usecase/migrationuc.Settings for configuration settings migration or the SchemaSettler interface from this package for database schema migration.

For simple resources like configuration settings, the settler instance is used to marshal and save the ultimate resource (or merge it with the target settings version default values as seen in the MergeSettings method of pkg/core/usecase/migrationuc.Settings interface). For more complex resources like database schema migration which had proceeded the migration without actually fetching and converting data items (but just kept some metadata about them by creating a series of views), the settler instance may be used to persist the migration results by creating the ultimate resources (e.g., tables which their rows are obtained by querying the previous/next version views). For more details, check the SchemaSettler interface.

type Tx

type Tx interface {
	Queryer

	// IsTx method prevents a non-Tx object (such as a Conn) to
	// mistakenly implement the Tx interface.
	IsTx()
}

Tx represents a database transaction. It is unsafe to be used concurrently. A transaction may be used in order to execute one or more SQL statements one at a time. For statement execution methods, see the Queryer interface. All statements which are in a single transaction observe the ACID properties. The exact amount of isolation between transactions depends on their types. By default, a READ-COMMITTED transaction is expected from a PostgreSQL DBMS server. For details, read https://www.postgresql.org/docs/current/transaction-iso.html#XACT-READ-COMMITTED

type TxHandler

type TxHandler func(context.Context, Tx) error

TxHandler is a handler function which takes a context and an ongoing transaction. If an error is returned, caller will rollback the transaction and in absence of errors, it will be committed.

type UpMigrator added in v1.1.0

type UpMigrator[S any] interface {
	Settler[S]

	// MigrateUp creates the next/upper version of the migrating
	// resource either physically (e.g., by creating a new settings
	// struct instance in order to maintain settings items in memory)
	// or logically (e.g., by creating some views in the upper version
	// format based on queries over views or tables from the current
	// version in order to proceed with a database schema migration).
	// Finally, another UpMigrator[S] instance will be created and
	// returned which describes the migrating resource at its upper
	// version, so the migration may continue.
	// If there is no more upper major version, an error will be
	// returned.
	MigrateUp(ctx context.Context) (UpMigrator[S], error)
}

UpMigrator of S interface specifies the upwards migrator objects requirements for a resource with S settler type. It embeds the Settler[S] interface and has one main method, the MigrateUp method. The S type parameter which represents the migration settler type can be filled, for example, by pkg/core/usecase/migrationuc.Settings for configuration settings migration or the SchemaSettler interface from this package for database schema migration. The MigrateUp method may be used to migrate one major version upwards and obtain the next relevant UpMigrator[S] instance, while the Settler method from the Settler[S] interface may be used to obtain an instance of S in order to settle the migration operation at its ultimate major version. The actual logic of migration settlement depends on the S type and is not defined by UpMigrator[S] itself.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL