crudiator

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Dec 27, 2023 License: MIT Imports: 7 Imported by: 0

README

crudiator

Supercharge your web backend development with crudiator.

Usage
  1. Define the schema
var (
	studentCrudiator = crudiator.MustNewEditor(
		"students",
		crudiator.POSTGRESQL,
		crudiator.NewField("id", crudiator.IsPrimaryKey, crudiator.IncludeOnRead),
		crudiator.NewField("name", crudiator.IncludeAlways),
		crudiator.NewField("age", crudiator.IncludeAlways),
		crudiator.NewField("created_at", crudiator.IncludeOnCreate, crudiator.IncludeOnRead),
		crudiator.NewField("updated_at", crudiator.IncludeOnUpdate, crudiator.IncludeOnRead),
		crudiator.NewField("school_id", crudiator.IncludeOnCreate, crudiator.IncludeOnRead, crudiator.IsSelectionFilter),
	).SoftDelete(true, "deleted_at").
		MustPaginate(crudiator.KEYSET, "id").
		Build()
)

studentCrudiator can be used concurrently since it does not store any state data.

  1. Call the CRUD functions by passing two things:
    • a form from which to pull values from
    • the *sql.DB handle to write and read values from.
studentCrudiator.Create(form, db)

Refer to tests for additional use cases

Pagination

Pagination is supported when reading data.

The following pagination strategies are implemented:

  1. Offset pagination - Uses OFFSET and LIMIT which allows skipping to specific offsets. However, as the size of data grows, it becomes significally slow since the entire table needs to be scanned in order to determine the offset.
  2. Keyset pagination - Utilizses an index to prevent full table scan. Implementation at the moment uses the following since most tables will have numeric keys:
WHERE indexedColumn > :lastIndexColumnValue:
ORDER BY indexedColumn ASC
FETCH NEXT 200 ROWS ONLY;

:lastIndexColumnValue: is the value of the previous value, initially 0, which automatically will return the first 200 rows.

The disadvantage of keyset pagination is that you cannot skip to a specific row offset.

Customization callbacks

There are two callback functions:

  • PreActionCallback
  • PostActionCallback

Each of these is invoked before and after each function of the CRUD is called.

Web Service Framework Support
Library Status Adapter
net/http YES nethttp
go-fiber NO gofiber
gin NO
Database support
Database Create Read Update Delete TESTED
PostgreSQL YES YES YES YES YES
MYSQL YES YES YES YES NO
SQLITE YES YES YES YES NO
Validation

crudiator does not provide data validation. Validation is not appropriate at this layer. It should be trivial to use data validators at a higher layer before passing data to crudiator.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CreateParameterPlaceholders

func CreateParameterPlaceholders(count int, dialect SQLDialect) string

func ParameterizeFields

func ParameterizeFields(fields []string, dialect SQLDialect, useAnd bool, startCountFrom ...int) string

Types

type Crudiator

type Crudiator interface {
	Create(form DataForm, db *sql.DB) (DbRow, error)
	Read(form DataForm, db *sql.DB, pageable ...Pageable) ([]DbRow, error)

	// Reads a single database row. May return nil,nil if no row exists
	SingleRead(form DataForm, db *sql.DB) (DbRow, error)
	// Updates the specified record and returns the updated row.
	//
	// A single statement is executed for postgres (using the RETURNING keyword) and for
	// all others, two statements are executed; one to update and one for the query.
	Update(form DataForm, db *sql.DB) (DbRow, error)

	Delete(form DataForm, db *sql.DB) (DbRow, error)
}

type DataForm

type DataForm interface {
	Has(name string) bool
	Get(name string) any
	Set(name string, value any)
	Remove(name string)
	Iterate(func(key string, value any))
}

func FromJsonStruct

func FromJsonStruct(structptr any, additional ...DataForm) DataForm

convert struct to DataForm

func FromStruct

func FromStruct(structptr any, tag string, additional ...DataForm) DataForm

Create form from a struct. Pass an additional form to append/overwrite values

func FromXmlStruct

func FromXmlStruct(structptr any, additional ...DataForm) DataForm

type DbRow

type DbRow map[string]any

Represents a database row (column set)

Since this is an aliased map type, it can readily be serialized.

Call DbRow.HasData() to check if any data was read into the row

func (DbRow) Get

func (row DbRow) Get(col string) any

func (DbRow) Has

func (row DbRow) Has(col string) bool

func (DbRow) HasData

func (row DbRow) HasData() bool

func (DbRow) Remove

func (row DbRow) Remove(col string)

func (DbRow) Scan

func (row DbRow) Scan(rows *sql.Rows) error

Scan columns at the current cursor into this row. sql.Rows.Next() must have already been called before calling this function.

type Editor

type Editor struct {
	// contains filtered or unexported fields
}

Editor is the object that interacts with the underlying object.

One instance can be used multiple times concurrently as no state is stored

func MustNewEditor

func MustNewEditor(table string, dialect SQLDialect, fields ...Field) *Editor

MustNewEditor Creates a table editor instance used for CRUD operations on that table.

The function will panic if fields is empty or if a duplicate field is found

func (*Editor) Build

func (e *Editor) Build() Crudiator

func (Editor) Create

func (e Editor) Create(form DataForm, db *sql.DB) (DbRow, error)

func (*Editor) Debug

func (e *Editor) Debug(b bool) *Editor

Toggles debug mode, which is equivalent to setting a Logger with DEBUG level

func (Editor) Delete

func (e Editor) Delete(form DataForm, db *sql.DB) (DbRow, error)

func (*Editor) MustPaginate

func (e *Editor) MustPaginate(strategy PaginationStrategy, fields ...string) *Editor

MustPaginate configures selection query pagination.

The function will panic if strategy is not 'KEYSET' and no fields have been defined

func (*Editor) OnPostCreate

func (e *Editor) OnPostCreate(f PostActionCallback) *Editor

func (*Editor) OnPostDelete added in v0.0.5

func (e *Editor) OnPostDelete(f PostActionCallback) *Editor

func (*Editor) OnPostRead

func (e *Editor) OnPostRead(f PostActionCallback) *Editor

func (*Editor) OnPostUpdate

func (e *Editor) OnPostUpdate(f PostActionCallback) *Editor

func (*Editor) OnPreCreate

func (e *Editor) OnPreCreate(f PreActionCallback) *Editor

func (*Editor) OnPreDelete added in v0.0.5

func (e *Editor) OnPreDelete(f PreActionCallback) *Editor

func (*Editor) OnPreRead

func (e *Editor) OnPreRead(f PreActionCallback) *Editor

func (*Editor) OnPreUpdate

func (e *Editor) OnPreUpdate(f PreActionCallback) *Editor

func (Editor) Read

func (e Editor) Read(form DataForm, db *sql.DB, pageable ...Pageable) ([]DbRow, error)

func (*Editor) SetLogger

func (e *Editor) SetLogger(l Logger) *Editor

Sets

func (Editor) SingleRead

func (e Editor) SingleRead(form DataForm, db *sql.DB) (DbRow, error)

func (*Editor) SoftDelete

func (e *Editor) SoftDelete(v bool) *Editor

SoftDelete Indicates whether records in this table should be soft deleted.

If true, a call to 'Delete' is converted to an 'Update', with only the columns marked for soft deletion being updated.

func (Editor) Update

func (e Editor) Update(form DataForm, db *sql.DB) (DbRow, error)

func (Editor) UsesKeysetPagination

func (e Editor) UsesKeysetPagination() bool

type Field

type Field struct {
	PrimaryKey bool
	Name       string
	Create     bool
	Read       bool
	Update     bool
	Unique     bool
	// Mark this field as a selection filter when 'Read()' is called
	//
	// Example
	//	studentEditor := MustNewEditor(
	//		"students",
	//		POSTGRESQL,
	//		NewField("name", IncludeAlways),
	//		NewField("school_id", IncludeAlways, IsSelectionFilter)
	//	)
	//	editor.Read() // SELECT "name", school_id FROM students WHERE school_id = $1
	SelectionFilter bool
	NullCheck       FieldNullCheck
	SoftDelete      bool      // Indicates whether this field should be used when soft-deleting records
	SoftDeleteType  FieldType // Indicates the type of the soft deletion field.
}

func NewField

func NewField(name string, options ...FieldOption) Field

type FieldNullCheck added in v0.0.3

type FieldNullCheck int

Indicates whether a field should be checked againt the 'NULL' constant value.

For most DBMS, the following query will yield nothing:

SELECT * FROM foo WHERE col1 = NULL

Rather, the following returns data:

SELECT * FROM foo WHERE col1 IS NULL

This type's values control this behavior. This is especially useful for filter fields

const (
	// Indicates that the field should always be checked normally '= ?' or '= $1'.
	// This is the default behavior
	NoFieldNullCheck FieldNullCheck = iota
	// Indicates that the field should always be checked as 'IS NULL'
	FieldMustBeNull
	// Indicates that the field should always be checked as 'IS NOT NULL'
	FieldMustNotBeNull
)

type FieldOption

type FieldOption func(f *Field)
var (
	IsPrimaryKey  FieldOption = func(f *Field) { f.PrimaryKey = true }
	IncludeAlways FieldOption = func(f *Field) {
		f.Read = true
		f.Create = true
		f.Update = true
	}
	IncludeOnCreate   FieldOption = func(f *Field) { f.Create = true }
	IncludeOnUpdate   FieldOption = func(f *Field) { f.Update = true }
	IncludeOnRead     FieldOption = func(f *Field) { f.Read = true }
	IsUnique          FieldOption = func(f *Field) { f.Unique = true }
	IsSelectionFilter FieldOption = func(f *Field) { f.SelectionFilter = true }
	IsNullConstant    FieldOption = func(f *Field) { f.NullCheck = FieldMustBeNull }
	IsNotNullConstant FieldOption = func(f *Field) { f.NullCheck = FieldMustNotBeNull }

	SoftDeleteAs = func(t FieldType) FieldOption {
		return func(f *Field) {
			f.SoftDelete = true
			f.SoftDeleteType = t
		}
	}
)

type FieldType added in v0.0.5

type FieldType int

Indicates the type of column the field represents, mainly used when soft-deleting.

The value for this field will automatically be set to true or its equivalent.

(int) = 1
(bool) = true
(timestamp/datetime) = time.Now()
const (
	IntField FieldType = iota
	BoolField
	TimestampField
)

type KeysetPaging

type KeysetPaging struct {
	Value    any
	PageSize int
}

func (KeysetPaging) KeysetValue

func (kp KeysetPaging) KeysetValue() any

func (KeysetPaging) Offset

func (kp KeysetPaging) Offset() int

func (KeysetPaging) Size

func (kp KeysetPaging) Size() int

type Level

type Level int
const (
	None Level = iota
	Debug
	Info
	Warn
	Error
)

type Logger

type Logger interface {
	Error(e error)
	Info(m string, args ...any)
	Warn(m string, args ...any)
	Debug(m string, args ...any)
}

func NewNopLogger

func NewNopLogger() Logger

func NewStdOutLogger

func NewStdOutLogger(lvl Level) Logger

type MapBackedDataForm

type MapBackedDataForm map[string]any

func (MapBackedDataForm) Get

func (f MapBackedDataForm) Get(name string) any

func (MapBackedDataForm) Has

func (f MapBackedDataForm) Has(name string) bool

func (MapBackedDataForm) Iterate added in v0.0.3

func (f MapBackedDataForm) Iterate(fn func(key string, value any))

func (MapBackedDataForm) Remove

func (f MapBackedDataForm) Remove(name string)

func (MapBackedDataForm) Set

func (f MapBackedDataForm) Set(name string, value any)

type OffsetPaging

type OffsetPaging struct {
	PageOffset int
	PageSize   int
}

func (OffsetPaging) KeysetValue

func (of OffsetPaging) KeysetValue() any

func (OffsetPaging) Offset

func (of OffsetPaging) Offset() int

func (OffsetPaging) Size

func (of OffsetPaging) Size() int

type Pageable

type Pageable interface {
	// Returns the calculated offset. Applicable in 'OFFSET' mode
	Offset() int

	// Returns the page size
	Size() int

	// Returns the indexable value to be used. Applicable in 'KEYSET' mode
	KeysetValue() any
}

Passed to the 'Retrieve()' function to be used for pagination

Only applicable if pagination has been configured through 'MustPaginate()' function.

func NewKeysetPaging

func NewKeysetPaging(value any, size int) Pageable

func NewOffsetPaging

func NewOffsetPaging(page, size int) Pageable

type PaginationStrategy

type PaginationStrategy int

Defines how to query a table. i.e select everything at once or paginate.

Default is NONE

const (
	// Do not paginate the query
	NONE PaginationStrategy = iota

	// Use offset pagination where an entire table is scanned in order to get to a specific offset.
	//
	// The larger the offset, the slower the query.
	OFFSET

	// Use keyset pagination where an indexed column is used for comparison.
	//
	// Provides faster pagination better than OFFSET based.
	KEYSET
)

type PostActionCallback

type PostActionCallback func(editor Editor, rows []DbRow)

type PreActionCallback

type PreActionCallback func(editor Editor, form DataForm)

type SQLDialect

type SQLDialect int

The dialect to use when interacting with the underlying database

const (
	MYSQL SQLDialect = iota + 1
	POSTGRESQL
	SQLITE
)

type StdOutLogger

type StdOutLogger struct {
	// contains filtered or unexported fields
}

func (StdOutLogger) Debug

func (l StdOutLogger) Debug(m string, args ...any)

func (StdOutLogger) Error

func (l StdOutLogger) Error(e error)

func (StdOutLogger) Info

func (l StdOutLogger) Info(m string, args ...any)

func (StdOutLogger) Warn

func (l StdOutLogger) Warn(m string, args ...any)

Directories

Path Synopsis
integration
nethttp
Provides facility to read request data from net/http request into a DataForm instance
Provides facility to read request data from net/http request into a DataForm instance

Jump to

Keyboard shortcuts

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