Skip to content

Commit 6b3e753

Browse files
committed
Add implementation of posts_at_version
1 parent ad41d9b commit 6b3e753

File tree

6 files changed

+237
-6
lines changed

6 files changed

+237
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- This file should undo anything in `up.sql`
2+
3+
ALTER TABLE posts DROP CONSTRAINT posts_pkey;
4+
ALTER TABLE posts ADD PRIMARY KEY (id);
5+
ALTER TABLE comments ADD FOREIGN KEY (post) REFERENCES posts(id) ON UPDATE CASCADE ON DELETE CASCADE;
6+
ALTER TABLE posts DROP COLUMN version_start;
7+
ALTER TABLE posts DROP COLUMN version_end;
8+
9+
DROP FUNCTION posts_at_version;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- Your SQL goes here
2+
3+
ALTER TABLE posts ADD COLUMN version_start INTEGER NOT NULL DEFAULT 0;
4+
ALTER TABLE posts ADD COLUMN version_end INTEGER DEFAULT NULL;
5+
ALTER TABLE comments DROP CONSTRAINT comments_post_fkey;
6+
ALTER TABLE posts DROP CONSTRAINT posts_pkey;
7+
ALTER TABLE posts ADD PRIMARY KEY (id, version_start);
8+
9+
CREATE OR REPLACE FUNCTION posts_at_version (version int DEFAULT NULL)
10+
RETURNS TABLE(id Integer, title Text, content Text, published_at Timestamp with time zone, author Integer, post_state post_state) AS $$
11+
DECLARE
12+
result record;
13+
BEGIN
14+
IF version IS NULL THEN
15+
RETURN QUERY SELECT posts.id, posts.title, posts.content, posts.published_at, posts.author, posts.post_state FROM posts WHERE projects.version_end IS NULL;
16+
ELSE
17+
RETURN QUERY SELECT posts.id, posts.title, posts.content, posts.published_at, posts.author, posts.post_state FROM posts WHERE int4range(version_start, version_end, '[)') @> version;
18+
END IF;
19+
END;
20+
$$ LANGUAGE plpgsql;

src/graphql/mod.rs

+64-5
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ use diesel::pg::Pg;
77
use diesel::prelude::*;
88
use juniper::{ExecutionResult, Executor, GraphQLInputObject, Selection, Value};
99
use wundergraph::prelude::*;
10-
use wundergraph::query_builder::mutations::{HandleBatchInsert, HandleInsert};
10+
use wundergraph::query_builder::mutations::{HandleBatchInsert, HandleInsert, HandleUpdate};
1111
use wundergraph::query_builder::selection::LoadingHandler;
1212
use wundergraph::scalar::WundergraphScalarValue;
1313

14-
#[derive(WundergraphEntity, Identifiable, Debug)]
14+
mod post_at_version;
15+
16+
use self::post_at_version::*;
17+
18+
#[derive(WundergraphEntity, Identifiable, Debug, Clone)]
1519
#[table_name = "users"]
1620
pub struct User {
1721
id: i32,
@@ -21,7 +25,7 @@ pub struct User {
2125
comments: HasMany<Comment, comments::author>,
2226
}
2327

24-
#[derive(WundergraphEntity, Identifiable, Debug)]
28+
#[derive(WundergraphEntity, Identifiable, Debug, Clone)]
2529
#[table_name = "posts"]
2630
pub struct Post {
2731
id: i32,
@@ -33,21 +37,24 @@ pub struct Post {
3337
post_state: PostState,
3438
}
3539

36-
#[derive(WundergraphEntity, Identifiable, Debug)]
40+
#[derive(WundergraphEntity, Identifiable, Debug, Clone)]
3741
#[table_name = "comments"]
3842
pub struct Comment {
3943
id: i32,
4044
comment: String,
4145
published_at: DateTime<Utc>,
4246
author: HasOne<i32, User>,
4347
post: HasOne<i32, Post>,
48+
#[column_name = "post"]
49+
posts_at_version: HasOne<i32, PostAtVersion>,
4450
}
4551

4652
wundergraph::query_object! {
4753
Query {
4854
User,
4955
Post,
5056
Comment,
57+
PostAtVersion(version: Option<i32>),
5158
}
5259
}
5360

@@ -58,7 +65,7 @@ pub struct UserChangeset {
5865
name: String,
5966
}
6067

61-
#[derive(GraphQLInputObject, Identifiable, AsChangeset)]
68+
#[derive(GraphQLInputObject, Identifiable)]
6269
#[table_name = "posts"]
6370
pub struct PostChangeset {
6471
id: i32,
@@ -100,6 +107,8 @@ impl HandleInsert<Post, NewPost, Pg, PgConnection> for posts::table {
100107
posts::content.eq(insertable.content),
101108
posts::author.eq(insertable.author),
102109
posts::post_state.eq(PostState::Draft),
110+
posts::version_start.eq(0),
111+
posts::version_end.eq(Option::<i32>::None),
103112
))
104113
.returning(posts::id)
105114
.get_result::<i32>(conn)?;
@@ -133,6 +142,8 @@ impl HandleBatchInsert<Post, NewPost, Pg, PgConnection> for posts::table {
133142
posts::content.eq(content),
134143
posts::author.eq(author),
135144
posts::post_state.eq(PostState::Draft),
145+
posts::version_start.eq(0),
146+
posts::version_end.eq(Option::<i32>::None),
136147
)
137148
},
138149
)
@@ -152,6 +163,54 @@ impl HandleBatchInsert<Post, NewPost, Pg, PgConnection> for posts::table {
152163
}
153164
}
154165

166+
impl HandleUpdate<Post, PostChangeset, Pg, PgConnection> for posts::table {
167+
fn handle_update(
168+
selection: Option<&'_ [Selection<'_, WundergraphScalarValue>]>,
169+
executor: &Executor<PgConnection, WundergraphScalarValue>,
170+
update: &PostChangeset,
171+
) -> ExecutionResult<WundergraphScalarValue> {
172+
let ctx = executor.context();
173+
let conn = ctx.get_connection();
174+
conn.transaction(|| {
175+
let current_version = posts::table
176+
.select(diesel::dsl::max(posts::version_start))
177+
.filter(posts::id.eq(update.id))
178+
.get_result::<Option<i32>>(conn)?
179+
.unwrap_or(0);
180+
181+
diesel::update(
182+
posts::table.filter(
183+
posts::id
184+
.eq(update.id)
185+
.and(posts::version_start.eq(current_version)),
186+
),
187+
)
188+
.set(posts::version_end.eq(Some(current_version + 1)))
189+
.execute(conn)?;
190+
191+
let inserted = diesel::insert_into(posts::table)
192+
.values((
193+
posts::id.eq(update.id),
194+
posts::title.eq(&update.title),
195+
posts::content.eq(&update.content),
196+
posts::author.eq(update.author),
197+
posts::post_state.eq(update.post_state),
198+
posts::version_start.eq(current_version + 1),
199+
posts::version_end.eq(Option::<i32>::None),
200+
))
201+
.returning(posts::id)
202+
.get_result::<i32>(conn)?;
203+
204+
let look_ahead = executor.look_ahead();
205+
206+
let query = <Post as LoadingHandler<_, PgConnection>>::build_query(&[], &look_ahead)?
207+
.filter(posts::id.eq(inserted));
208+
let items = Post::load(&look_ahead, selection, executor, query)?;
209+
Ok(items.into_iter().next().unwrap_or(Value::Null))
210+
})
211+
}
212+
}
213+
155214
wundergraph::mutation_object! {
156215
Mutation {
157216
User(insert = NewUser, update = UserChangeset, delete = true),

src/graphql/post_at_version.rs

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
use crate::from_sql_function;
2+
use crate::graphql::{Comment, User};
3+
use crate::model::posts::PostState;
4+
use crate::schema::*;
5+
use chrono::{DateTime, Utc};
6+
use diesel::associations::HasTable;
7+
use diesel::connection::Connection;
8+
use diesel::pg::Pg;
9+
use diesel::prelude::*;
10+
use diesel::query_builder::BoxedSelectStatement;
11+
use diesel::query_dsl::methods;
12+
use diesel::Identifiable;
13+
use juniper::{LookAheadArgument, LookAheadMethods, LookAheadSelection};
14+
use wundergraph::error::Result;
15+
use wundergraph::graphql_type::{GraphqlWrapper, WundergraphGraphqlMapper};
16+
use wundergraph::juniper_ext::FromLookAheadValue;
17+
use wundergraph::query_builder::selection::fields::WundergraphBelongsTo;
18+
use wundergraph::query_builder::selection::filter::{
19+
BuildFilter, BuildFilterHelper, FilterWrapper,
20+
};
21+
use wundergraph::query_builder::selection::BoxedQuery;
22+
use wundergraph::query_builder::selection::LoadingHandler;
23+
use wundergraph::query_builder::types::{HasMany, HasOne};
24+
use wundergraph::scalar::WundergraphScalarValue;
25+
use wundergraph::WundergraphContext;
26+
27+
from_sql_function! {
28+
posts_at_version(version: Nullable<Integer>) {
29+
id -> Int4,
30+
title -> Text,
31+
content -> Nullable<Text>,
32+
published_at -> Timestamptz,
33+
author -> Int4,
34+
post_state -> crate::model::posts::Post_state,
35+
}
36+
}
37+
38+
#[derive(Clone, Debug, BuildFilterHelper, WundergraphBelongsTo)]
39+
#[table_name = "posts_at_version"]
40+
pub struct PostAtVersion {
41+
id: i32,
42+
title: String,
43+
content: Option<String>,
44+
published_at: DateTime<Utc>,
45+
author: HasOne<i32, User>,
46+
comments: HasMany<Comment, comments::post>,
47+
post_state: PostState,
48+
}
49+
50+
impl HasTable for PostAtVersion {
51+
type Table = posts_at_version::posts_at_version;
52+
53+
fn table() -> Self::Table {
54+
unimplemented!()
55+
}
56+
}
57+
58+
impl<'a> Identifiable for &'a PostAtVersion {
59+
type Id = &'a i32;
60+
61+
fn id(self) -> Self::Id {
62+
&self.id
63+
}
64+
}
65+
66+
impl<Ctx> LoadingHandler<Pg, Ctx> for PostAtVersion
67+
where
68+
Ctx: WundergraphContext + 'static,
69+
Ctx::Connection: Connection<Backend = Pg>,
70+
{
71+
type Columns = (
72+
posts_at_version::id,
73+
posts_at_version::title,
74+
posts_at_version::content,
75+
posts_at_version::published_at,
76+
posts_at_version::author,
77+
posts_at_version::post_state,
78+
);
79+
type FieldList = (
80+
i32,
81+
String,
82+
Option<String>,
83+
DateTime<Utc>,
84+
HasOne<i32, User>,
85+
HasMany<Comment, comments::post>,
86+
PostState,
87+
);
88+
type PrimaryKeyIndex = wundergraph::helper::TupleIndex0;
89+
type Filter = FilterWrapper<Self, Pg, Ctx>;
90+
const FIELD_NAMES: &'static [&'static str] = &[
91+
"id",
92+
"title",
93+
"content",
94+
"published_at",
95+
"author",
96+
"comments",
97+
"post_state",
98+
];
99+
const TYPE_NAME: &'static str = "PostsAtVersion";
100+
101+
fn build_query<'a>(
102+
_global_args: &[LookAheadArgument<WundergraphScalarValue>],
103+
select: &LookAheadSelection<'_, WundergraphScalarValue>,
104+
) -> Result<BoxedQuery<'a, Self, Pg, Ctx>>
105+
where
106+
Self::Table: methods::BoxedDsl<
107+
'a,
108+
Pg,
109+
Output = BoxedSelectStatement<
110+
'a,
111+
diesel::dsl::SqlTypeOf<<Self::Table as Table>::AllColumns>,
112+
Self::Table,
113+
Pg,
114+
>,
115+
> + 'static,
116+
<Self::Filter as BuildFilter<Pg>>::Ret: AppearsOnTable<Self::Table>,
117+
{
118+
let version: Option<i32> = select
119+
.argument("version")
120+
.and_then(|v| FromLookAheadValue::from_look_ahead(&v.value()));
121+
let mut query = posts_at_version(version)
122+
.into_boxed()
123+
.select(<Self as LoadingHandler<Pg, Ctx>>::get_select(select)?);
124+
125+
query = <Self as LoadingHandler<Pg, Ctx>>::apply_filter(query, select)?;
126+
query = <Self as LoadingHandler<Pg, Ctx>>::apply_limit(query, select)?;
127+
query = <Self as LoadingHandler<Pg, Ctx>>::apply_offset(query, select)?;
128+
query = <Self as LoadingHandler<Pg, Ctx>>::apply_order(query, select)?;
129+
130+
Ok(query)
131+
}
132+
}
133+
134+
impl<Ctx> WundergraphGraphqlMapper<Pg, Ctx> for PostAtVersion
135+
where
136+
Ctx: WundergraphContext + 'static,
137+
Ctx::Connection: Connection<Backend = diesel::pg::Pg>,
138+
{
139+
type GraphQLType = GraphqlWrapper<PostAtVersion, Pg, Ctx>;
140+
}

src/model/posts.rs

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ pub struct Post {
4141
published_at: DateTime<Utc>,
4242
author: i32,
4343
post_state: PostState,
44+
version_start: i32,
45+
version_end: Option<i32>,
4446
}
4547

4648
#[derive(Deserialize, Debug, AsChangeset, GraphQLInputObject)]

src/schema.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ table! {
2222
published_at -> Timestamptz,
2323
author -> Int4,
2424
post_state -> Post_state,
25+
version_start -> Int4,
26+
version_end -> Nullable<Int4>,
2527
}
2628
}
2729

@@ -36,7 +38,6 @@ table! {
3638
}
3739
}
3840

39-
joinable!(comments -> posts (post));
4041
joinable!(comments -> users (author));
4142
joinable!(posts -> users (author));
4243

0 commit comments

Comments
 (0)