
domata-skunk_sjs1_3.0.12.5.source-code.Queries.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of edomata-skunk_sjs1_3 Show documentation
Show all versions of edomata-skunk_sjs1_3 Show documentation
Skunk based backend for edomata
The newest version!
/*
* Copyright 2021 Hossein Naderi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package edomata.skunk
import _root_.skunk.*
import _root_.skunk.codec.all.*
import _root_.skunk.implicits.*
import edomata.backend.*
import edomata.backend.eventsourcing.AggregateState
import edomata.core.CommandMessage
import edomata.core.MessageMetadata
import java.time.Instant
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.util.UUID
private[skunk] object Queries {
def setupSchema(namespace: PGNamespace): Command[Void] =
sql"""create schema if not exists "#$namespace";""".command
final class Journal[E](namespace: PGNamespace, codec: BackendCodec[E]) {
private val table = sql""""#$namespace".journal"""
private val event = codec.codec
def setup: Command[Void] = sql"""
DO $$$$ begin
CREATE TABLE IF NOT EXISTS $table (
id uuid NOT NULL,
"time" timestamptz NOT NULL,
seqnr bigserial NOT NULL,
"version" int8 NOT NULL,
stream text NOT NULL,
payload #${codec.oid.name} NOT NULL,
CONSTRAINT journal_pk PRIMARY KEY (id),
CONSTRAINT journal_un UNIQUE (stream, version)
);
CREATE INDEX IF NOT EXISTS journal_seqnr_idx ON $table USING btree (seqnr);
CREATE INDEX IF NOT EXISTS journal_stream_idx ON $table USING btree (stream, version);
END $$$$;
""".command
final case class InsertRow(
id: UUID,
streamId: String,
time: OffsetDateTime,
version: SeqNr,
event: E
)
private val insertRow: Codec[InsertRow] =
(uuid *: text *: timestamptz *: int8 *: event).to[InsertRow]
def append(
n: List[InsertRow]
): Command[n.type] =
sql"""insert into $table ("id", "stream", "time", "version", "payload") values ${insertRow.values
.list(n)}""".command
private val readFields = sql"id, time, seqnr, version, stream, payload"
private val metaCodec: Codec[EventMetadata] =
(uuid *: timestamptz *: int8 *: int8 *: text).to
private val readCodec: Codec[EventMessage[E]] =
(metaCodec *: event).to
def readAll: Query[Void, EventMessage[E]] =
sql"select $readFields from $table order by seqnr asc".query(readCodec)
def readAllAfter: Query[Long, EventMessage[E]] =
sql"select $readFields from $table where seqnr > $int8 order by seqnr asc"
.query(readCodec)
def readAllBefore: Query[Long, EventMessage[E]] =
sql"select $readFields from $table where seqnr < $int8 order by seqnr asc"
.query(readCodec)
def readStream: Query[String, EventMessage[E]] =
sql"select $readFields from $table where stream = $text order by version asc"
.query(readCodec)
def readStreamAfter: Query[(String, Long), EventMessage[E]] =
sql"select $readFields from $table where stream = $text and version > $int8 order by version asc"
.query(readCodec)
def readStreamBefore: Query[(String, Long), EventMessage[E]] =
sql"select $readFields from $table where stream = $text and version < $int8 order by version asc"
.query(readCodec)
}
final class Outbox[N](namespace: PGNamespace, codec: BackendCodec[N]) {
private val table = sql""""#$namespace".outbox"""
private val notification = codec.codec
val setup = sql"""
CREATE TABLE IF NOT EXISTS $table(
seqnr bigserial NOT NULL,
stream text NOT NULL,
correlation text NULL,
causation text NULL,
payload #${codec.oid.name} NOT NULL,
created timestamptz NOT NULL,
published timestamptz NULL,
CONSTRAINT outbox_pk PRIMARY KEY (seqnr)
);
""".command
def markAsPublished(l: List[Long]): Command[(OffsetDateTime, l.type)] =
sql"""
update $table
set published = $timestamptz
where seqnr in ${int8.list(l).values}
""".command
private val metadata: Codec[MessageMetadata] = (text.opt *: text.opt).to
private val itemCodec: Codec[OutboxItem[N]] =
(int8 *: text *: timestamptz *: notification *: metadata).to
val read: Query[Void, OutboxItem[N]] =
sql"""
select seqnr, stream, created, payload, correlation, causation
from $table
where published is NULL
order by seqnr asc
""".query(itemCodec)
type BatchInsert = List[(N, String, OffsetDateTime, MessageMetadata)]
private val insertCodec = (notification *: text *: timestamptz *: metadata)
def insertAll(items: BatchInsert): Command[items.type] =
sql"""
insert into $table (payload, stream, created, correlation, causation) values ${insertCodec.values
.list(items)}
""".command
}
final class Snapshot[S](
namespace: PGNamespace,
codec: BackendCodec[S]
) {
private val table = sql""""#$namespace".snapshots"""
private val state = codec.codec
val setup: Command[Void] = sql"""
CREATE TABLE IF NOT EXISTS $table (
id text NOT NULL,
"version" int8 NOT NULL,
state #${codec.oid.name} NOT NULL,
CONSTRAINT snapshots_pk PRIMARY KEY (id)
);
""".command
private def aggregateStateCodec: Codec[AggregateState.Valid[S]] =
(state *: int8).to
private val insertCodec = (text *: aggregateStateCodec).values
def put(l: List[(String, AggregateState.Valid[S])]): Command[l.type] =
sql"""
insert into $table (id, state, "version") values ${insertCodec.list(l)}
on conflict (id) do update
set version = excluded.version,
state = excluded.state
""".command
def get: Query[String, AggregateState.Valid[S]] =
sql"""select state , version from $table where id = $text""".query(
aggregateStateCodec
)
}
final class Commands(namespace: PGNamespace) {
private val table = sql""""#$namespace".commands"""
val setup: Command[Void] = sql"""
CREATE TABLE IF NOT EXISTS $table (
id text NOT NULL,
"time" timestamptz NOT NULL,
address text NOT NULL,
CONSTRAINT commands_pk PRIMARY KEY (id)
);
""".command
val count: Query[String, Long] =
sql"select count(*) from $table where id = $text".query(int8)
private val instant: Codec[Instant] =
timestamptz.imap(_.toInstant)(_.atOffset(ZoneOffset.UTC))
private val command: Encoder[CommandMessage[?]] =
(text *: text *: instant).contramap(c => (c.id, c.address, c.time))
def insert: Command[CommandMessage[?]] = sql"""
insert into $table (id, address, "time") values ($command);
""".command
}
final class State[S](
namespace: PGNamespace,
codec: BackendCodec[S]
) {
private val table = sql""""#$namespace".states"""
private val state = codec.codec
val setup: Command[Void] = sql"""
CREATE TABLE IF NOT EXISTS $table (
id text NOT NULL,
"version" int8 NOT NULL,
state #${codec.oid.name} NOT NULL,
CONSTRAINT states_pk PRIMARY KEY (id)
);
""".command
import cqrs.AggregateState
private def aggregateStateCodec: Codec[AggregateState[S]] =
(state *: int8).to
def put: Command[String *: S *: SeqNr *: EmptyTuple] =
sql"""
insert into $table (id, state, "version") values ($text, $state, 1)
on conflict (id) do update
set version = $table.version + 1,
state = excluded.state
where $table.version = $int8
""".command
def get: Query[String, AggregateState[S]] =
sql"""select state , version from $table where id = $text""".query(
aggregateStateCodec
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy