/*-------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation.  All rights reserved.
 *
 * include/aggregation/bson_aggregation_pipeline.h
 *
 * Exports for the bson_aggregation_pipeline definition
 *
 *-------------------------------------------------------------------------
 */

#ifndef BSON_AGGREGATION_PIPELINE_H
#define BSON_AGGREGATION_PIPELINE_H

#include <nodes/params.h>

#include "operators/bson_expression.h"
#include "utils/documentdb_errors.h"
#include "utils/version_utils.h"
#include "metadata/collection.h"

typedef enum QueryCursorType
{
	QueryCursorType_Unspecified = 0,

	/*
	 * Whether or not the query can be done as a streamable
	 * query.
	 */
	QueryCursorType_Streamable = 1,

	/*
	 * Indicates whether this is processed as a single batch query.
	 */
	QueryCursorType_SingleBatch,

	/*
	 * The cursor plan is a point read.
	 */
	QueryCursorType_PointRead,

	/*
	 * Whether or not the query can be done as a tailable
	 * query.
	 */
	QueryCursorType_Tailable,

	/*
	 * By default all queries are persistent cursors.
	 */
	QueryCursorType_Persistent,

	/*
	 * Indicates that the query may need to switch between persistent
	 * and streaming behavior depending on the execution context.
	 */
	QueryCursorType_Dynamic,
} QueryCursorType;


/*
 * The kind of cursor parameters that should be generated by the query.
 */
typedef enum CursorParamKind
{
	/* The cursor parameters should be geared towards a persistent cursor.
	 * This is the default behavior and works like regular Postgres queries.
	 */
	CursorParamKind_Persistent = 0,

	/*
	 * The cursor parameters should be geared towards a streaming cursor.
	 * This is used when the query is expected to return results in a
	 * streamable fashion for simple finds and aggregates.
	 */
	CursorParamKind_Streaming = 1,

	/*
	 * The cursor parameters should be geared towards a dynamic cursor.
	 * This is used when the query may need to switch between persistent
	 * and streaming behavior depending on the execution context.
	 */
	CursorParamKind_Dynamic = 2,
} CursorParamKind;

/*
 * Tracks the collection topology relevant to cursor behavior.
 * This is set during initial query generation and then reported
 * during getMore operations.
 */
typedef enum CursorTopology
{
	CursorTopology_Unknown = 0,
	CursorTopology_LocalUnsharded,
	CursorTopology_RemoteUnsharded,
	CursorTopology_ShardedWithShardKeyEquality,
	CursorTopology_ShardedWithInOnShardKey,
	CursorTopology_GeneralSharded,
} CursorTopology;

/*
 * Tracks the overall query spec data
 * that can be extracted from the query.
 * Used in cursor management to page and
 * decide what kind of cursor to use for the outer
 * request.
 */
typedef struct
{
	/*
	 * The parameter number used for the cursor
	 * continuation (if it is a streaming cursor)
	 */
	int cursorStateParamNumber;

	/* Optional cursor state const param */
	pgbson *cursorStateConst;

	/*
	 * The namespaceName associated with the query.
	 */
	const char *namespaceName;

	QueryCursorType cursorKind;

	/*
	 * The collection topology for this cursor query.
	 */
	CursorTopology cursorTopology;

	/*
	 * The requested batchSize in the query request.
	 */
	int32_t batchSize;

	/*
	 * The maxAwaitTimeMS in the query request for tailable cursors.
	 */
	int64_t maxAwaitTimeMS;

	/*
	 * The time system variables ($$NOW, $$CLUSTER_TIME).
	 */
	TimeSystemVariables timeSystemVariables;

	/*
	 * Whether this query is being built from the planner aggregation
	 * query cursor rewrite path (enableCursorsOnAggregationQueryRewrite).
	 */
	bool isAggregationQueryCursorRewrite;
} QueryData;


Query * GenerateFindQuery(text *database, pgbson *findSpec, QueryData *queryData,
						  CursorParamKind cursorParamKind, bool setStatementTimeout);

/*
 * Opaque handle for the two-phase find query path: ParseFindQueryAndLookupCollection
 * parses the spec and looks up the collection (without generating the query), and
 * ApplyParsedFindQuery generates the local query from the parsed handle.
 */
typedef struct FindQueryPlan FindQueryPlan;
FindQueryPlan * ParseFindQueryAndLookupCollection(text *database, pgbson *findSpec,
												  QueryData *queryData,
												  bool setStatementTimeout,
												  MongoCollection **outCollection);
Query * ApplyParsedFindQuery(FindQueryPlan *plan, CursorParamKind cursorParamKind);
Query * GenerateGetMoreQuery(text *database, pgbson *getMoreSpec,
							 pgbson *continuationSpec,
							 QueryData *queryData, bool setStatementTimeout);
Query * BuildAggregationCursorGetMoreQuery(text *database, pgbson *getMoreSpec,
										   pgbson *continuationSpec);
Query * GenerateCountQuery(text *database, pgbson *countSpec, bool setStatementTimeout);
Query * GenerateDistinctQuery(text *database, pgbson *distinctSpec, bool
							  setStatementTimeout);
Query * GenerateListCollectionsQuery(text *database, pgbson *listCollectionsSpec,
									 QueryData *queryData, bool setStatementTimeout);
Query * GenerateListIndexesQuery(text *database, pgbson *listIndexesSpec,
								 QueryData *queryData, bool setStatementTimeout);

Query * GenerateAggregationQuery(text *database, pgbson *aggregationSpec,
								 QueryData *queryData, CursorParamKind cursorParamKind,
								 bool setStatementTimeout);

/*
 * Opaque handle for the two-phase aggregation query path:
 * ParseAggregationQueryAndLookupCollection parses the spec, extracts the pipeline
 * stages, and looks up the collection (without generating the query);
 * ApplyParsedAggregationQuery generates the local query from the parsed handle.
 */
typedef struct AggregationQueryPlan AggregationQueryPlan;
AggregationQueryPlan * ParseAggregationQueryAndLookupCollection(text *database,
																pgbson *aggregationSpec,
																QueryData *queryData,
																CursorParamKind
																cursorParamKind,
																bool setStatementTimeout,
																MongoCollection **
																outCollection);
Query * ApplyParsedAggregationQuery(AggregationQueryPlan *plan);

int64_t ParseGetMore(text **databaseName, pgbson *getMoreSpec, QueryData *queryData, bool
					 setStatementTimeout, bool isTailableCursor);

void ValidateAggregationPipeline(text *databaseDatum, const StringView *baseCollection,
								 const bson_value_t *pipelineValue);

void LookupExtractCollectionAndPipeline(const bson_value_t *lookupValue,
										StringView *collection, bson_value_t *pipeline);

void GraphLookupExtractCollection(const bson_value_t *lookupValue,
								  StringView *collection);
void ParseUnionWith(const bson_value_t *existingValue, StringView *collectionFrom,
					bson_value_t *pipeline);
void ParseInputDocumentForTopAndBottom(const bson_value_t *inputDocument,
									   bson_value_t *input,
									   bson_value_t *elementsToFetch,
									   bson_value_t *sortSpec, const char *opName);
void ParseInputDocumentForMedianAndPercentile(const bson_value_t *inputDocument,
											  bson_value_t *input, bson_value_t *p,
											  bson_value_t *method, bool isMedianOp);
void ValidateElementForNGroupAccumulators(bson_value_t *elementsToFetch, const
										  char *opName);
void ParseInputForNGroupAccumulators(const bson_value_t *inputDocument,
									 bson_value_t *input,
									 bson_value_t *elementsToFetch,
									 const char *opName);

extern int DefaultCursorFirstPageBatchSize;

/* Generates a base QueryData used for the first page */
inline static QueryData
GenerateFirstPageQueryData(void)
{
	QueryData queryData = { 0 };
	queryData.batchSize = DefaultCursorFirstPageBatchSize;
	return queryData;
}


/*
 * Feature flag and version check for using optimized "WithExpr" aggregate functions
 * (e.g., bsonmaxwithexpr, bsonminwithexpr, etc.) instead of the legacy aggregates
 * that wrap bson_expression_get.
 *
 * Used by both $group accumulators and $setWindowFields window operators.
 */
extern bool EnableNewMinMaxAccumulators;
extern bool EnableNewWithExprAccumulators;
extern bool EnableCollationWithNewGroupAccumulators;

inline static bool
CanUseWithExprMinMaxAggregates(void)
{
	return (EnableNewMinMaxAccumulators || EnableNewWithExprAccumulators) &&
		   IsClusterVersionAtleast(DocDB_V0, 110, 0);
}


/*
 * Feature flag and version check for using the superset EnableNewWithExprAccumulators
 * GUC. Gates WithExpr aggregates introduced in v111.
 */
inline static bool
CanUseWithExprAggregates(void)
{
	return EnableNewWithExprAccumulators &&
		   IsClusterVersionAtleast(DocDB_V0, 111, 0);
}


#endif
