FreeGrid Python server helper
=============================

freegrid_query.py   one file, standard library only, Python 3.10+

  read_request(body)    reads restDatasource's request (paging, sort, filters, quick filter, advanced filter, grouping)
  GridQuery(...).build  turns it into SQL: where, order_by, page, args, count_args, group
  to_rows(rows, total)  the {"rows", "total"} response, dates as "YYYY-MM-DD"
  read_save(body)       {"created", "updated", "deleted"} from saveChanges()

  query = GridQuery(
      columns={"orderNo": Column("ORDER_NO", "text"), "qty": Column("QTY", "number")},
      dialect="limit",        # or "standard" (OFFSET ? ROWS FETCH NEXT ? ROWS ONLY)
      paramstyle="qmark",     # your driver's: qmark, format, pyformat, named or numeric
  )
  sql = query.build(read_request(body))
  cur.execute("SELECT ... FROM ORDERS" + sql.where + sql.order_by + sql.page, sql.args)

Works with any DB-API 2.0 driver (sqlite3, psycopg, PyMySQL, oracledb, pyodbc) and any framework
(FastAPI, Flask, Django views).

Only the columns you list can be sorted, filtered or grouped; every value is a bound parameter.
A bad number or date, an unknown filter op or too many values raises GridError: answer 400 { message }.
LIKE searches use ESCAPE '!', so % and _ typed in a filter are matched as text.

What the grid sends and what to answer, with a full example server:
  https://freegrid.app/docs/server/     (English)
  https://freegrid.app/ko/docs/server/  (한국어)

Use is covered by the FreeGrid license: https://freegrid.app/pricing/
