postgres_cursor add

This commit is contained in:
jasinco 2025-04-25 11:45:55 +08:00
parent dc71938bd8
commit 0745298257
9 changed files with 131 additions and 82 deletions

BIN
bun.lockb Normal file → Executable file

Binary file not shown.

4
justfile Normal file
View file

@ -0,0 +1,4 @@
fake:
POSTGRES_URL="postgres://test:test@192.168.50.14:5432/posts" bun run ./tools/gen_fake.ts
fetch_post:
POSTGRES_URL="postgres://test:test@192.168.50.14:5432/posts" bun run ./tools/post_db.ts

View file

@ -9,6 +9,7 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "^9.7.0",
"bun-types": "^1.2.9", "bun-types": "^1.2.9",
"next": "15.3.0", "next": "15.3.0",
"react": "^19.0.0", "react": "^19.0.0",

View file

@ -1,4 +1,3 @@
import { retrieve_post } from "@/db";
export default function Home() { export default function Home() {
@ -8,7 +7,7 @@ export default function Home() {
<h1 className="text-4xl mt-4 ml-3"></h1> <h1 className="text-4xl mt-4 ml-3"></h1>
</div> </div>
<div className="h-[90dvh] w-[85dvw] absolute left-1/2 top-1/2 mt-5 -translate-1/2"> <div className="h-[90dvh] w-[85dvw] absolute left-1/2 top-1/2 mt-5 -translate-1/2">
</div> </div>
</div> </div>
); );

View file

@ -1,35 +1,35 @@
import { Suspense } from "react"; import { Suspense } from "react";
import Image from "next/image"; import Image from "next/image";
import { Attachment } from "@/db"; import { Attachment, MultiMediaType } from "@/db";
export default function Post(post_content: string, attachments: Attachment[]) { export default function Post(post_content: string, attachments: Attachment[]) {
let images = []; let images = [];
let videos = []; let videos = [];
attachments.forEach(attachment => { attachments.forEach(attachment => {
if (attachment.type.type == "video") { if (attachment.type == MultiMediaType.video) {
attachment.urls.forEach(url => { attachment.urls.forEach(url => {
videos.push( videos.push(
<Suspense fallback={<p></p>}> <Suspense fallback={<p></p>}>
<video controls preload="none" aria-label="Video player"> <video controls preload="none" aria-label="Video player">
<source src={url} type={attachment.type.toString()} /> <source src={url} type={attachment.type.toString()} />
Your browser does not support the video tag. Your browser does not support the video tag.
</video> </video>
</Suspense> </Suspense>
) )
}) })
}else if (attachment.type.type="image"){ } else if (attachment.type == MultiMediaType.image) {
attachment.urls.forEach(url => { attachment.urls.forEach(url => {
images.push(<Image src={url} alt="Uploaded" width={300} height={200}></Image>) images.push(<Image src={url} alt="Uploaded" width={300} height={200}></Image>)
}) })
} }
}) })
return (<div className="w-full h-fit"> return (<div className="w-full h-fit">
<div> <div>
{post_content} {post_content}
</div> </div>
<div> <div>
<div></div> <div></div>
</div> </div>
</div>) </div>)
} }

112
src/db.ts
View file

@ -1,57 +1,73 @@
import { main } from "bun"; import { env, ReservedSQL, sql } from 'bun'
import { sql } from "bun"; import { MIMEType } from 'util';
import { MIMEType } from "util"
export enum MultiMediaType {
video, image
}
export interface Attachment { export interface Attachment {
type: MIMEType; urls: string[],
urls: string[]; type: MultiMediaType
} };
export interface Post { export interface Post {
content: string, post: string,
post_time: string,
hash: string, hash: string,
attachments: Attachment[],
}
interface SQLPostCast {
hash: string, post: string, post_time: string, images: number[]
};
const SQLPostCast2Post = (obj: SQLPostCast) => {
let x: Post =
{
post: obj.post,
hash: obj.hash,
post_time: obj.post_time,
attachments: []
};
if (obj.images && obj.images.length > 0) {
x.attachments.push(
{
type: MultiMediaType.image, urls: obj.images.map(img => {
return `/img/${obj.hash}_${img}`
})
}
)
}
return x
} }
export async function setup_func() { export interface Cursor {
const rows = await sql` hash: string,
CREATE OR REPLACE FUNCTION fetch_post( post_time: string,
OUT p_text VARCHAR(1000),
OUT p_hash CHAR(32),
OUT p_date TIMESTAMP,
OUT p_pics INT[]
)
RETURNS SETOF RECORD AS
$$
DECLARE
post_cursor CURSOR FOR
SELECT content, hash, date
FROM posts;
post_record RECORD;
BEGIN
-- Open cursor
OPEN post_cursor;
-- Fetch rows and return
LOOP
FETCH NEXT FROM post_cursor INTO post_record;
EXIT WHEN NOT FOUND;
p_text = post_record.content;
p_date = post_record.date;
p_hash = post_record.hash;
RETURN NEXT;
END LOOP;
-- Close cursor
CLOSE post_cursor;
END;
$$
LANGUAGE PLPGSQL;`;
} }
/* retrieve the latest post with posts table */
export async function retrieve_post(offset: Number) { export class PostFetcher {
const res = await sql`SELECT * FROM fetch_post();` conn: Promise<ReservedSQL>;
console.log(res) constructor() {
this.conn = sql.reserve();
}
async init() {
await this.conn.then(async e => {
await e`DECLARE post_ptr CURSOR WITH HOLD FOR SELECT niming.posts.*, niming.images.file_sequence
AS images FROM niming.posts
LEFT JOIN niming.images ON niming.images.hash=niming.posts.hash ORDER BY niming.posts.post_time DESC, niming.posts.hash ASC`
}).catch(err => {
console.log(err);
})
}
async postgres_fetch() {
let x: SQLPostCast[] = []
if (this.conn) {
x = await (await this.conn)`FETCH 10 IN post_ptr`;
}
return x.map(e => {
return SQLPostCast2Post(e)
})
}
} }
setup_func()
retrieve_post(0)

15
tools/gen_fake.ts Normal file
View file

@ -0,0 +1,15 @@
import { CryptoHasher, sql } from "bun";
import { retrieve_post, setup_func } from "@/db";
import { faker } from '@faker-js/faker';
for (let i = 0; i < 100; i++) {
let text = faker.string.alpha(20);
let hash = new CryptoHasher("sha256")
hash.update(text)
hash.update(Date.now().toString())
let dg = hash.digest("hex")
console.log(dg)
const _ = await sql`INSERT INTO niming.posts (hash, post,post_time) VALUES (${dg},${text}, now())`
}

13
tools/post_db.ts Normal file
View file

@ -0,0 +1,13 @@
import { PostFetcher, Post } from "@/db"
import { sql } from "bun";
let x: Post[] = [];
const fetcher = new PostFetcher();
await fetcher.init()
for (let i = 0; i < 10; i++) {
x = await fetcher.postgres_fetch()
console.log(x)
}

View file

@ -8,7 +8,7 @@
"noEmit": true, "noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "bundler", "moduleResolution": "Bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
@ -20,7 +20,8 @@
], ],
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
} },
"types":["bun-types"]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]