packages feed

moonlight-core-0.1.0.0: src-term/Moonlight/Core/Term/Database/Transaction.hs

{-# LANGUAGE QuantifiedConstraints #-}

module Moonlight.Core.Term.Database.Transaction where

import Control.Monad.ST (ST, runST)
import Data.Functor (void)
import Data.STRef
  ( modifySTRef',
    newSTRef,
    readSTRef,
    writeSTRef,
  )
import Moonlight.Core.DenseKey (DenseKey)
import Moonlight.Core.Language (Language)
import Moonlight.Core.Term.Database.Command
import Moonlight.Core.Term.Database.Table
import Moonlight.Core.Term.Database.Types
import Prelude

runCommandTransaction ::
  (Ord key, Language f) =>
  Database f key ->
  (forall s. DatabaseEditor s f key -> ST s result) ->
  (result, [TermCommand f key], Database f key)
runCommandTransaction =
  runTransactionWithCommands normalizeTermCommands
{-# INLINE runCommandTransaction #-}

runTransactionWithCommands ::
  ([TermCommand f key] -> [TermCommand f key]) ->
  Database f key ->
  (forall s. DatabaseEditor s f key -> ST s result) ->
  (result, [TermCommand f key], Database f key)
runTransactionWithCommands normalizeResidualCommands database action =
  runST $ do
    workingRef <- newSTRef database
    residualRef <- newSTRef []
    controlRef <- newSTRef CommitDatabaseTransaction
    let editor =
          DatabaseEditor
            { workingRef = workingRef,
              residualCommandsRef = residualRef,
              controlRef = controlRef
            }
    result <- action editor
    control <- readSTRef controlRef
    workingDatabase <- readSTRef workingRef
    residualCommands <- readSTRef residualRef
    let (committedCommands, committedDatabase) =
          case control of
            CommitDatabaseTransaction ->
              (normalizeResidualCommands residualCommands, workingDatabase)
            AbortDatabaseTransaction ->
              ([], database)
    pure (result, committedCommands, committedDatabase)
{-# INLINE runTransactionWithCommands #-}

editorSnapshot :: DatabaseEditor s f key -> ST s (Database f key)
editorSnapshot =
  readSTRef . workingRef
{-# INLINE editorSnapshot #-}

editorQueueCommands ::
  (DenseKey key, Language f) =>
  [TermCommand f key] ->
  DatabaseEditor s f key ->
  ST s (TermCommitResult f key)
editorQueueCommands commands editor =
  modifyEditorWithResiduals editor (commitTermCommands commands)
{-# INLINE editorQueueCommands #-}

editorInsertTuple ::
  (DenseKey key, Language f) =>
  key ->
  f key ->
  DatabaseEditor s f key ->
  ST s ()
editorInsertTuple resultValue tupleValue editor =
  void (editorQueueCommands [InsertTerm resultValue tupleValue] editor)
{-# INLINE editorInsertTuple #-}

editorDeleteRow ::
  (forall a. Ord a => Ord (f a)) =>
  Operator f ->
  RowId ->
  DatabaseEditor s f key ->
  ST s ()
editorDeleteRow operator rowId editor =
  modifyEditor editor (deleteRow operator rowId)
{-# INLINE editorDeleteRow #-}

editorDeleteTuple ::
  (DenseKey key, Language f) =>
  key ->
  f key ->
  DatabaseEditor s f key ->
  ST s ()
editorDeleteTuple resultValue tupleValue editor =
  modifyEditor editor (deleteTuple resultValue tupleValue)
{-# INLINE editorDeleteTuple #-}

editorReplaceTuple ::
  (DenseKey key, Language f) =>
  key ->
  f key ->
  key ->
  f key ->
  DatabaseEditor s f key ->
  ST s ()
editorReplaceTuple oldResult oldTuple newResult newTuple editor =
  void $
    modifyEditorWithResiduals
      editor
      (commitTermCommands [InsertTerm newResult newTuple] . deleteTuple oldResult oldTuple)
{-# INLINE editorReplaceTuple #-}

editorCompact :: Foldable f => DatabaseEditor s f key -> ST s ()
editorCompact editor =
  modifyEditor editor compact
{-# INLINE editorCompact #-}

abortTransaction :: DatabaseEditor s f key -> ST s ()
abortTransaction editor =
  writeSTRef (controlRef editor) AbortDatabaseTransaction
{-# INLINE abortTransaction #-}

modifyEditor :: DatabaseEditor s f key -> (Database f key -> Database f key) -> ST s ()
modifyEditor editor update =
  modifySTRef' (workingRef editor) update
{-# INLINE modifyEditor #-}

modifyEditorWithResiduals ::
  DatabaseEditor s f key ->
  (Database f key -> TermCommitResult f key) ->
  ST s (TermCommitResult f key)
modifyEditorWithResiduals editor update = do
  database <- readSTRef (workingRef editor)
  let commitResult = update database
  writeSTRef (workingRef editor) (committedDatabase commitResult)
  appendEditorResiduals editor (residualCommands commitResult)
  pure commitResult
{-# INLINE modifyEditorWithResiduals #-}

appendEditorResiduals :: DatabaseEditor s f key -> [TermCommand f key] -> ST s ()
appendEditorResiduals editor residualCommands =
  modifySTRef'
    (residualCommandsRef editor)
    (residualCommands <>)
{-# INLINE appendEditorResiduals #-}