@@ -301,6 +301,88 @@ func (p *Parser) parseJoinCondition(joinType string, isNatural, isApply bool) (a
301301 return nil , p .expectedError ("ON or USING" )
302302}
303303
304+ // parseSampleClause parses the ClickHouse SAMPLE clause that specifies data sampling.
305+ // It is called when the current token is SAMPLE (already verified by caller).
306+ //
307+ // Supported forms:
308+ //
309+ // SAMPLE 0.1 — ratio (floating-point fraction, e.g., 10%)
310+ // SAMPLE 1000 — approximate row count (integer)
311+ // SAMPLE 1/10 — fractional form (numerator/denominator)
312+ // SAMPLE 1/10 OFFSET 2/10 — fractional with an offset fraction
313+ func (p * Parser ) parseSampleClause () (* ast.SampleClause , error ) {
314+ samplePos := p .currentLocation ()
315+ p .advance () // Consume SAMPLE
316+
317+ if p .isType (models .TokenTypeEOF ) || p .isType (models .TokenTypeSemicolon ) {
318+ return nil , goerrors .ExpectedTokenError (
319+ "sampling size after SAMPLE" ,
320+ p .currentToken .Token .Type .String (),
321+ p .currentLocation (),
322+ "SAMPLE clause requires a numeric argument" ,
323+ )
324+ }
325+
326+ clause := & ast.SampleClause {Pos : samplePos }
327+
328+ // Read the primary sampling value (numerator / whole number / float)
329+ if ! p .isNumericLiteral () {
330+ return nil , goerrors .ExpectedTokenError (
331+ "numeric literal after SAMPLE" ,
332+ p .currentToken .Token .Type .String (),
333+ p .currentLocation (),
334+ "SAMPLE clause requires a numeric argument (ratio, row count, or N/D fraction)" ,
335+ )
336+ }
337+ clause .Value = p .currentToken .Token .Value
338+ p .advance ()
339+
340+ // Check for fractional form: SAMPLE N / D
341+ if p .isType (models .TokenTypeDiv ) {
342+ p .advance () // consume /
343+ if ! p .isNumericLiteral () {
344+ return nil , goerrors .ExpectedTokenError (
345+ "denominator after /" ,
346+ p .currentToken .Token .Type .String (),
347+ p .currentLocation (),
348+ "SAMPLE N/D fraction requires an integer denominator" ,
349+ )
350+ }
351+ clause .Denominator = p .currentToken .Token .Value
352+ p .advance ()
353+ }
354+
355+ // Optional OFFSET N/D
356+ if p .isTokenMatch ("OFFSET" ) {
357+ p .advance () // Consume OFFSET
358+ if ! p .isNumericLiteral () {
359+ return nil , goerrors .ExpectedTokenError (
360+ "numeric literal after SAMPLE ... OFFSET" ,
361+ p .currentToken .Token .Type .String (),
362+ p .currentLocation (),
363+ "SAMPLE OFFSET requires a numeric argument" ,
364+ )
365+ }
366+ clause .Offset = p .currentToken .Token .Value
367+ p .advance ()
368+ if p .isType (models .TokenTypeDiv ) {
369+ p .advance () // consume /
370+ if ! p .isNumericLiteral () {
371+ return nil , goerrors .ExpectedTokenError (
372+ "denominator after OFFSET /" ,
373+ p .currentToken .Token .Type .String (),
374+ p .currentLocation (),
375+ "SAMPLE OFFSET N/D fraction requires an integer denominator" ,
376+ )
377+ }
378+ clause .OffsetDenominator = p .currentToken .Token .Value
379+ p .advance ()
380+ }
381+ }
382+
383+ return clause , nil
384+ }
385+
304386// parsePrewhereClause parses "PREWHERE <expr>" if present (ClickHouse-specific).
305387// PREWHERE is a ClickHouse optimisation that filters data blocks before reading
306388// all columns. It is semantically similar to WHERE but executed earlier in the
0 commit comments