Skip to content

Commit

Permalink
add expiration to cache
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacharrisholt committed Sep 3, 2024
1 parent 0a8653b commit a6ea431
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 16 deletions.
6 changes: 4 additions & 2 deletions src/pevensie/cache.gleam
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import gleam/option.{type Option}
import pevensie/drivers.{
type CacheDriver, type Connected, type Disabled, type Disconnected,
}
Expand Down Expand Up @@ -25,10 +26,11 @@ pub fn store(
resource_type: String,
key: String,
value: String,
ttl_seconds: Option(Int),
) -> Result(Nil, Nil) {
let assert cache.CacheConfig(driver) = pevensie.cache_config

driver.store(driver.driver, resource_type, key, value)
driver.store(driver.driver, resource_type, key, value, ttl_seconds)
}

pub fn get(
Expand All @@ -41,7 +43,7 @@ pub fn get(
),
resource_type: String,
key: String,
) -> Result(String, Nil) {
) -> Result(Option(String), Nil) {
let assert cache.CacheConfig(driver) = pevensie.cache_config

driver.get(driver.driver, resource_type, key)
Expand Down
5 changes: 3 additions & 2 deletions src/pevensie/drivers.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import gleam/dynamic.{type Decoder}
import gleam/json
import gleam/option.{type Option}
import pevensie/internal/user.{type User, type UserInsert}

pub type Connected
Expand Down Expand Up @@ -41,10 +42,10 @@ pub type AuthDriver(driver, user_metadata) {
}

type CacheStoreFunction(cache_driver) =
fn(cache_driver, String, String, String) -> Result(Nil, Nil)
fn(cache_driver, String, String, String, Option(Int)) -> Result(Nil, Nil)

type CacheGetFunction(cache_driver) =
fn(cache_driver, String, String) -> Result(String, Nil)
fn(cache_driver, String, String) -> Result(Option(String), Nil)

type CacheDeleteFunction(cache_driver) =
fn(cache_driver, String, String) -> Result(Nil, Nil)
Expand Down
49 changes: 37 additions & 12 deletions src/pevensie/drivers/postgres.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import gleam/dynamic.{
type DecodeErrors as DynamicDecodeErrors, type Decoder,
DecodeError as DynamicDecodeError,
}
import gleam/function
import gleam/erlang/process
import gleam/int
import gleam/io
import gleam/json
import gleam/option.{type Option, None, Some}
Expand Down Expand Up @@ -378,8 +379,8 @@ pub fn new_cache_driver(config: PostgresConfig) -> CacheDriver(Postgres) {
driver: Postgres(config |> postgres_config_to_pgo_config, None),
connect: connect,
disconnect: disconnect,
store: fn(driver, resource_type, key, value) {
store(driver, resource_type, key, value)
store: fn(driver, resource_type, key, value, ttl_seconds) {
store(driver, resource_type, key, value, ttl_seconds)
// TODO: Handle errors
|> result.map_error(fn(err) {
io.debug(err)
Expand Down Expand Up @@ -410,19 +411,26 @@ fn store(
resource_type: String,
key: String,
value: String,
ttl_seconds: Option(Int),
) -> Result(Nil, PostgresError) {
let assert Postgres(_, Some(conn)) = driver

let sql =
"
let expires_at_sql = case ttl_seconds {
None -> "null"
Some(ttl_seconds) ->
"now() + interval '" <> int.to_string(ttl_seconds) <> " seconds'"
}
let sql = "
insert into pevensie.\"cache\" (
resource_type,
key,
value
value,
expires_at
) values (
$1,
$2,
$3
$3,
" <> expires_at_sql <> "
)
on conflict (resource_type, key) do update set value = $3"

Expand All @@ -446,12 +454,16 @@ fn get(
driver: Postgres,
resource_type: String,
key: String,
) -> Result(String, PostgresError) {
) -> Result(Option(String), PostgresError) {
let assert Postgres(_, Some(conn)) = driver

let sql =
"
select value::text
select
value::text,
-- Returns true only if the exporation time is
-- set and has passed
(expires_at is not null and expires_at < now()) as expired
from pevensie.\"cache\"
where resource_type = $1 and key = $2"

Expand All @@ -460,14 +472,27 @@ fn get(
sql,
conn,
[pgo.text(resource_type), pgo.text(key)],
dynamic.decode1(function.identity, dynamic.element(0, dynamic.string)),
dynamic.decode2(
fn(value, expired) { #(value, expired) },
dynamic.element(0, dynamic.string),
dynamic.element(1, dynamic.bool),
),
)
|> result.map_error(QueryError)

use response <- result.try(query_result)
case response.rows {
[] -> Error(NotFound)
[value] -> Ok(value)
[] -> Ok(None)
// If no expiration is set, the value is valid forever
[#(value, False)] -> {
Ok(Some(value))
}
// If the value has expired, return None and delete the key
// in an async task
[#(_, True)] -> {
process.start(fn() { delete(driver, resource_type, key) }, False)
Ok(None)
}
_ -> Error(InternalError("Unexpected number of rows returned"))
}
}
Expand Down
1 change: 1 addition & 0 deletions v1.sql
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,6 @@ create table if not exists pevensie."cache" (
resource_type text not null,
key text not null unique,
value text not null,
expires_at timestamptz,
primary key (resource_type, key)
);

0 comments on commit a6ea431

Please sign in to comment.