11import { db } from '@sim/db'
22import { memory , workspace } from '@sim/db/schema'
3- import { and , count , desc , eq , isNull , lt , max , or , sql } from 'drizzle-orm'
4- import { TABLE_LIMITS } from '@/lib/table/constants'
3+ import { and , count , desc , eq , inArray , isNull , lt , max , or , sql } from 'drizzle-orm'
4+ import { getMaxPageBytes , TABLE_LIMITS } from '@/lib/table/constants'
55import { TableQueryValidationError } from '@/lib/table/errors'
66import {
77 buildFilterClause ,
@@ -38,6 +38,16 @@ function referencesMemoryTranscript(value: unknown): boolean {
3838
3939function createMemoryRowsQuery ( ) {
4040 const messageCount = sql < number > `CASE WHEN jsonb_typeof(${ memory . data } ) = 'array' THEN jsonb_array_length(${ memory . data } ) ELSE 0 END`
41+ const createdAtIso = sql < string > `to_char(${ memory . createdAt } , 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`
42+ const updatedAtIso = sql < string > `to_char(${ memory . updatedAt } , 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`
43+ const rowData = sql < JsonValue > `jsonb_build_object(
44+ ${ MEMORY_TABLE_COLUMNS . id } ::text, ${ memory . id } ,
45+ ${ MEMORY_TABLE_COLUMNS . conversationId } ::text, ${ memory . key } ,
46+ ${ MEMORY_TABLE_COLUMNS . transcript } ::text, ${ memory . data } ,
47+ ${ MEMORY_TABLE_COLUMNS . messageCount } ::text, ${ messageCount } ,
48+ ${ MEMORY_TABLE_COLUMNS . createdAt } ::text, ${ createdAtIso } ,
49+ ${ MEMORY_TABLE_COLUMNS . updatedAt } ::text, ${ updatedAtIso }
50+ )`
4151 return db
4252 . select ( {
4353 id : memory . id ,
@@ -48,12 +58,13 @@ function createMemoryRowsQuery() {
4858 deletedAt : memory . deletedAt ,
4959 transcript : sql < JsonValue > `${ memory . data } ` . as ( 'transcript' ) ,
5060 messageCount : messageCount . mapWith ( Number ) . as ( 'message_count' ) ,
61+ rowBytes : sql < number > `octet_length((${ rowData } )::text)` . mapWith ( Number ) . as ( 'row_bytes' ) ,
5162 data : sql < JsonValue > `jsonb_build_object(
5263 ${ MEMORY_TABLE_COLUMNS . id } ::text, ${ memory . id } ,
5364 ${ MEMORY_TABLE_COLUMNS . conversationId } ::text, ${ memory . key } ,
5465 ${ MEMORY_TABLE_COLUMNS . messageCount } ::text, ${ messageCount } ,
55- ${ MEMORY_TABLE_COLUMNS . createdAt } ::text, ${ memory . createdAt } ,
56- ${ MEMORY_TABLE_COLUMNS . updatedAt } ::text, ${ memory . updatedAt }
66+ ${ MEMORY_TABLE_COLUMNS . createdAt } ::text, ${ createdAtIso } ,
67+ ${ MEMORY_TABLE_COLUMNS . updatedAt } ::text, ${ updatedAtIso }
5768 )` . as ( 'data' ) ,
5869 } )
5970 . from ( memory )
@@ -176,8 +187,8 @@ export async function queryMemoryTableRows({
176187 key : memoryRows . key ,
177188 createdAt : memoryRows . createdAt ,
178189 updatedAt : memoryRows . updatedAt ,
179- data : memoryRows . transcript ,
180190 messageCount : memoryRows . messageCount ,
191+ rowBytes : memoryRows . rowBytes ,
181192 } )
182193 . from ( memoryRows )
183194 . where ( pageWhere )
@@ -189,24 +200,69 @@ export async function queryMemoryTableRows({
189200 ? db . select ( { value : count ( ) } ) . from ( memoryRows ) . where ( baseWhere )
190201 : Promise . resolve ( null )
191202 const [ candidates , totalRows ] = await Promise . all ( [ candidatePromise , totalPromise ] )
192- const rows = candidates . map ( ( candidate , index ) =>
193- mapMemoryRecordToTableRow (
194- {
195- id : candidate . id ,
196- key : candidate . key ,
197- data : candidate . data ,
198- messageCount : candidate . messageCount ,
199- createdAt : candidate . createdAt ,
200- updatedAt : candidate . updatedAt ,
201- } ,
202- offset + index
203- )
204- )
203+ const pageByteBudget = getMaxPageBytes ( ) ?? TABLE_LIMITS . MAX_QUERY_RESULT_BYTES
204+ const selectedCandidates : typeof candidates = [ ]
205+ let selectedBytes = 0
206+ let hasMore = false
207+
208+ for ( const candidate of candidates ) {
209+ const rowBytes = Number ( candidate . rowBytes )
210+ if ( ! Number . isFinite ( rowBytes ) || rowBytes < 0 ) {
211+ throw new TableQueryValidationError ( 'Memory table returned an invalid row size' )
212+ }
213+ if ( selectedCandidates . length === 0 && rowBytes > pageByteBudget ) {
214+ throw new TableQueryValidationError (
215+ `Memory transcript exceeds the ${ Math . floor ( pageByteBudget / ( 1024 * 1024 ) ) } MB query response limit` ,
216+ 'TABLE_QUERY_RESULT_TOO_LARGE'
217+ )
218+ }
219+ if ( selectedCandidates . length > 0 && selectedBytes + rowBytes > pageByteBudget ) {
220+ hasMore = true
221+ break
222+ }
223+ selectedCandidates . push ( candidate )
224+ selectedBytes += rowBytes
225+ }
226+
227+ const selectedIds = selectedCandidates . map ( ( candidate ) => candidate . id )
228+ const transcripts =
229+ selectedIds . length > 0
230+ ? await db
231+ . select ( { id : memory . id , data : sql < JsonValue > `${ memory . data } ` } )
232+ . from ( memory )
233+ . where (
234+ and (
235+ eq ( memory . workspaceId , workspaceId ) ,
236+ isNull ( memory . deletedAt ) ,
237+ inArray ( memory . id , selectedIds )
238+ )
239+ )
240+ . limit ( selectedIds . length )
241+ : [ ]
242+ const transcriptById = new Map ( transcripts . map ( ( record ) => [ record . id , record . data ] ) )
243+ const rows = selectedCandidates . flatMap ( ( candidate , index ) => {
244+ const transcript = transcriptById . get ( candidate . id )
245+ if ( transcript === undefined ) return [ ]
246+ return [
247+ mapMemoryRecordToTableRow (
248+ {
249+ id : candidate . id ,
250+ key : candidate . key ,
251+ data : transcript ,
252+ messageCount : candidate . messageCount ,
253+ createdAt : candidate . createdAt ,
254+ updatedAt : candidate . updatedAt ,
255+ } ,
256+ offset + index
257+ ) ,
258+ ]
259+ } )
205260
206261 return {
207262 rows,
208263 totalCount : totalRows ? Number ( totalRows [ 0 ] . value ) : null ,
209264 keysetValid : ! sort ,
265+ hasMore,
210266 }
211267}
212268/** Searches Memory cells in PostgreSQL while preserving the active view's row ordinals. */
0 commit comments