postgres_cursor add
This commit is contained in:
		
							parent
							
								
									dc71938bd8
								
							
						
					
					
						commit
						0745298257
					
				
					 9 changed files with 131 additions and 82 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								bun.lockb
									
										
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bun.lockb
									
										
									
									
									
										
										
										Normal file → Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										4
									
								
								justfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								justfile
									
										
									
									
									
										Normal 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 | ||||
|  | @ -9,6 +9,7 @@ | |||
|     "lint": "next lint" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@faker-js/faker": "^9.7.0", | ||||
|     "bun-types": "^1.2.9", | ||||
|     "next": "15.3.0", | ||||
|     "react": "^19.0.0", | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| import { retrieve_post } from "@/db"; | ||||
| 
 | ||||
| export default function Home() { | ||||
| 
 | ||||
|  | @ -8,7 +7,7 @@ export default function Home() { | |||
|         <h1 className="text-4xl mt-4 ml-3">匿名中工</h1> | ||||
|       </div> | ||||
|       <div className="h-[90dvh] w-[85dvw] absolute left-1/2 top-1/2 mt-5 -translate-1/2"> | ||||
|          | ||||
| 
 | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
|  |  | |||
|  | @ -1,35 +1,35 @@ | |||
| 
 | ||||
| import { Suspense } from "react"; | ||||
| import Image from "next/image"; | ||||
| import { Attachment } from "@/db"; | ||||
| import { Attachment, MultiMediaType } from "@/db"; | ||||
| 
 | ||||
| export default function Post(post_content: string, attachments: Attachment[]) { | ||||
|     let images = []; | ||||
|     let videos = []; | ||||
|     attachments.forEach(attachment => { | ||||
|         if (attachment.type.type == "video") { | ||||
|             attachment.urls.forEach(url => { | ||||
|                 videos.push( | ||||
|                     <Suspense fallback={<p>加載中</p>}> | ||||
|                         <video controls preload="none" aria-label="Video player"> | ||||
|                             <source src={url} type={attachment.type.toString()} /> | ||||
|                             Your browser does not support the video tag. | ||||
|                         </video> | ||||
|                     </Suspense> | ||||
|                 ) | ||||
|             }) | ||||
|         }else if (attachment.type.type="image"){ | ||||
|             attachment.urls.forEach(url => { | ||||
|                 images.push(<Image src={url} alt="Uploaded" width={300} height={200}></Image>) | ||||
|             }) | ||||
|         } | ||||
|     }) | ||||
|     return (<div className="w-full h-fit"> | ||||
|         <div> | ||||
|             {post_content} | ||||
|         </div> | ||||
|         <div> | ||||
|             <div></div> | ||||
|         </div> | ||||
|     </div>) | ||||
| } | ||||
|   let images = []; | ||||
|   let videos = []; | ||||
|   attachments.forEach(attachment => { | ||||
|     if (attachment.type == MultiMediaType.video) { | ||||
|       attachment.urls.forEach(url => { | ||||
|         videos.push( | ||||
|           <Suspense fallback={<p>加載中</p>}> | ||||
|             <video controls preload="none" aria-label="Video player"> | ||||
|               <source src={url} type={attachment.type.toString()} /> | ||||
|               Your browser does not support the video tag. | ||||
|             </video> | ||||
|           </Suspense> | ||||
|         ) | ||||
|       }) | ||||
|     } else if (attachment.type == MultiMediaType.image) { | ||||
|       attachment.urls.forEach(url => { | ||||
|         images.push(<Image src={url} alt="Uploaded" width={300} height={200}></Image>) | ||||
|       }) | ||||
|     } | ||||
|   }) | ||||
|   return (<div className="w-full h-fit"> | ||||
|     <div> | ||||
|       {post_content} | ||||
|     </div> | ||||
|     <div> | ||||
|       <div></div> | ||||
|     </div> | ||||
|   </div>) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										112
									
								
								src/db.ts
									
										
									
									
									
								
							
							
						
						
									
										112
									
								
								src/db.ts
									
										
									
									
									
								
							|  | @ -1,57 +1,73 @@ | |||
| import { main } from "bun"; | ||||
| import { sql } from "bun"; | ||||
| import { MIMEType } from "util" | ||||
| import { env, ReservedSQL, sql } from 'bun' | ||||
| import { MIMEType } from 'util'; | ||||
| 
 | ||||
| export enum MultiMediaType { | ||||
|   video, image | ||||
| } | ||||
| 
 | ||||
| export interface Attachment { | ||||
|   type: MIMEType; | ||||
|   urls: string[]; | ||||
| } | ||||
|   urls: string[], | ||||
|   type: MultiMediaType | ||||
| }; | ||||
| 
 | ||||
| export interface Post { | ||||
|   content: string, | ||||
|   post: string, | ||||
|   post_time: 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() { | ||||
|   const rows = await sql` | ||||
|   CREATE OR REPLACE FUNCTION fetch_post( | ||||
|    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;`;
 | ||||
| export interface Cursor { | ||||
|   hash: string, | ||||
|   post_time: string, | ||||
| } | ||||
| 
 | ||||
| /* retrieve the latest post with posts table */ | ||||
| export async function retrieve_post(offset: Number) { | ||||
|   const res = await sql`SELECT * FROM fetch_post();` | ||||
|   console.log(res) | ||||
| 
 | ||||
| export class PostFetcher { | ||||
|   conn: Promise<ReservedSQL>; | ||||
|   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
									
								
							
							
						
						
									
										15
									
								
								tools/gen_fake.ts
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										13
									
								
								tools/post_db.ts
									
										
									
									
									
										Normal 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) | ||||
| 
 | ||||
| } | ||||
|  | @ -8,7 +8,7 @@ | |||
|     "noEmit": true, | ||||
|     "esModuleInterop": true, | ||||
|     "module": "esnext", | ||||
|     "moduleResolution": "bundler", | ||||
|     "moduleResolution": "Bundler", | ||||
|     "resolveJsonModule": true, | ||||
|     "isolatedModules": true, | ||||
|     "jsx": "preserve", | ||||
|  | @ -20,7 +20,8 @@ | |||
|     ], | ||||
|     "paths": { | ||||
|       "@/*": ["./src/*"] | ||||
|     } | ||||
|     }, | ||||
|     "types":["bun-types"] | ||||
|   }, | ||||
|   "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], | ||||
|   "exclude": ["node_modules"] | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue