@@ -188,33 +188,54 @@ public void forEachUser(java.util.function.BiConsumer<UUID, ArrayList<Column>> p
188188 java .util .function .Consumer <Integer > onFinished ) {
189189
190190 int processed = 0 ;
191- final int pageSize = 500 ; // tune 250/500/1000
191+ final int pageSize = 500 ;
192192
193- String lastUuid = "" ; // seek cursor (string form)
193+ // Use a typed cursor. NULL means "start from beginning".
194+ UUID lastUuidPg = null ;
195+ String lastUuidMy = null ;
194196
195197 while (true ) {
196198 int rowsThisPage = 0 ;
197199
198- // Read a page into memory, CLOSE connection, then run callbacks.
199- // This prevents holding a DB connection while user code runs.
200200 final ArrayList <java .util .AbstractMap .SimpleEntry <UUID , ArrayList <Column >>> page = new ArrayList <>(
201201 pageSize );
202202
203- String sqlStr = "SELECT * FROM " + qi (tableName ) + " WHERE " + qi ("uuid" ) + " > ? ORDER BY " + qi ("uuid" )
204- + " ASC LIMIT " + pageSize + ";" ;
203+ final String sqlStr ;
204+ if (dbType == DbType .POSTGRESQL ) {
205+ // Typed cursor + safe first page
206+ sqlStr = "SELECT * FROM " + qi (tableName ) + " WHERE (?::uuid IS NULL OR " + qi ("uuid" ) + " > ?::uuid) "
207+ + " ORDER BY " + qi ("uuid" ) + " ASC " + " LIMIT " + pageSize + ";" ;
208+ } else {
209+ // MySQL/MariaDB uuid stored as VARCHAR(37) so string comparison works
210+ sqlStr = "SELECT * FROM " + qi (tableName ) + " WHERE " + qi ("uuid" ) + " > ? " + " ORDER BY " + qi ("uuid" )
211+ + " ASC " + " LIMIT " + pageSize + ";" ;
212+ }
205213
206214 try (Connection conn = mysql .getConnectionManager ().getConnection ();
207215 PreparedStatement ps = conn .prepareStatement (sqlStr , ResultSet .TYPE_FORWARD_ONLY ,
208216 ResultSet .CONCUR_READ_ONLY )) {
209217
210- ps .setString (1 , lastUuid );
218+ if (dbType == DbType .POSTGRESQL ) {
219+ // Bind BOTH placeholders as UUID (or NULL)
220+ if (lastUuidPg == null ) {
221+ ps .setNull (1 , java .sql .Types .OTHER );
222+ ps .setNull (2 , java .sql .Types .OTHER );
223+ } else {
224+ ps .setObject (1 , lastUuidPg );
225+ ps .setObject (2 , lastUuidPg );
226+ }
227+ } else {
228+ // First page: "" is OK for varchar seek
229+ ps .setString (1 , (lastUuidMy == null ) ? "" : lastUuidMy );
230+ }
211231
212232 try (ResultSet rs = ps .executeQuery ()) {
213233 final ResultSetMetaData meta = rs .getMetaData ();
214234 final int colCount = meta .getColumnCount ();
215235
216236 while (rs .next ()) {
217237 ArrayList <Column > cols = new ArrayList <>(colCount );
238+
218239 UUID uuid = null ;
219240 String uuidStrForSeek = null ;
220241
@@ -238,14 +259,15 @@ public void forEachUser(java.util.function.BiConsumer<UUID, ArrayList<Column>> p
238259 rCol .setValue (new DataValueInt (0 ));
239260 }
240261 }
262+
241263 } else if (plugin .getUserManager ().getDataManager ().isBoolean (columnName )) {
242264 rCol = new Column (columnName , DataType .BOOLEAN );
243265 rCol .setValue (new DataValueBoolean (Boolean .valueOf (rs .getString (i ))));
266+
244267 } else {
245268 rCol = new Column (columnName , DataType .STRING );
246269
247270 if ("uuid" .equalsIgnoreCase (columnName )) {
248- // MySQL stores as string; Postgres might store native UUID, handle both
249271 if (dbType == DbType .POSTGRESQL ) {
250272 Object obj = rs .getObject (i );
251273 if (obj instanceof java .util .UUID ) {
@@ -280,10 +302,13 @@ public void forEachUser(java.util.function.BiConsumer<UUID, ArrayList<Column>> p
280302 rowsThisPage ++;
281303 processed ++;
282304
283- // advance seek cursor based on the last row we read
284- lastUuid = uuidStrForSeek ;
305+ // advance cursor based on the last row
306+ if (dbType == DbType .POSTGRESQL ) {
307+ lastUuidPg = uuid ;
308+ } else {
309+ lastUuidMy = uuidStrForSeek ;
310+ }
285311
286- // store for later callback (after connection closes)
287312 page .add (new java .util .AbstractMap .SimpleEntry <>(uuid , cols ));
288313 }
289314 }
@@ -294,17 +319,15 @@ public void forEachUser(java.util.function.BiConsumer<UUID, ArrayList<Column>> p
294319 break ;
295320 }
296321
297- // Run callbacks OUTSIDE of DB resources
322+ // Run callbacks outside DB resources
298323 for (java .util .AbstractMap .SimpleEntry <UUID , ArrayList <Column >> entry : page ) {
299324 try {
300325 perUser .accept (entry .getKey (), entry .getValue ());
301326 } catch (Throwable t ) {
302- // Don't kill the whole scan if user-code throws
303327 debug (t );
304328 }
305329 }
306330
307- // no more rows
308331 if (rowsThisPage == 0 ) {
309332 break ;
310333 }
0 commit comments