You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2026/09/13 14:37:21 UTC

(cayenne) 03/03: Query API cleanup - XML property parsing moved to

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit 607cb15a7d1584e1f66c49b91704614f7189488b
Author: Andrus Adamchik <an...@objectstyle.com>
AuthorDate: Sun Sep 13 10:36:09 2026 -0400

    Query API cleanup - XML property parsing moved to
    
    descriptors where it belongs. Descriptors now init queries
---
 .../editor/procedure/ProcedureQueryView.java       |   8 +-
 .../editor/query/ObjectQueryPropertiesPanel.java   |   5 +-
 .../editor/query/RawQueryPropertiesPanel.java      |   5 +-
 .../editor/query/SelectPropertiesPanel.java        |  43 ++---
 .../query/selectquery/SelectQueryMainTab.java      |   6 +-
 .../query/sqltemplate/SQLTemplateMainTab.java      |   9 +-
 .../project/validation/BaseQueryValidator.java     |   3 +-
 .../apache/cayenne/wocompat/EOModelProcessor.java  |  13 +-
 .../apache/cayenne/map/EJBQLQueryDescriptor.java   |   8 +-
 .../cayenne/map/ProcedureQueryDescriptor.java      |  36 +++-
 .../org/apache/cayenne/map/QueryDescriptor.java    | 198 ++++++++++++++++++++-
 .../apache/cayenne/map/SQLTemplateDescriptor.java  |  36 +++-
 .../apache/cayenne/map/SelectQueryDescriptor.java  |  14 +-
 .../apache/cayenne/query/BaseQueryMetadata.java    |  50 ------
 .../java/org/apache/cayenne/query/EJBQLQuery.java  |   9 -
 .../org/apache/cayenne/query/FluentSelect.java     |   7 -
 .../org/apache/cayenne/query/ProcedureQuery.java   |  23 ---
 .../org/apache/cayenne/query/QueryMetadata.java    |  42 +----
 .../java/org/apache/cayenne/query/SQLTemplate.java |  20 ---
 .../cayenne/map/EJBQLQueryDescriptorTest.java      |  25 +++
 .../cayenne/map/ProcedureQueryDescriptorTest.java  |  54 ++++++
 .../apache/cayenne/map/QueryDescriptorTest.java    | 107 +++++++++++
 .../cayenne/map/SQLTemplateDescriptorTest.java     |  40 ++++-
 .../cayenne/map/SelectQueryDescriptorTest.java     |  51 +++++-
 24 files changed, 586 insertions(+), 226 deletions(-)

diff --git a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureQueryView.java b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureQueryView.java
index b2ce9e375..d6fb56530 100644
--- a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureQueryView.java
+++ b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/procedure/ProcedureQueryView.java
@@ -42,7 +42,6 @@ import org.apache.cayenne.modeler.toolkit.text.CMUndoableTextField;
 import org.apache.cayenne.modeler.project.ProjectComparators;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.query.CapsStrategy;
-import org.apache.cayenne.query.ProcedureQuery;
 import java.util.Objects;
 import org.apache.cayenne.validation.ValidationException;
 
@@ -291,7 +290,7 @@ public class ProcedureQueryView extends ProjectPanel {
             labelCase.setRenderer(new LabelCapsRenderer());
             labelCase.addActionListener(event -> {
                 String value = labelCase.getModel().getSelectedItem().toString();
-                setQueryProperty(ProcedureQuery.COLUMN_NAME_CAPITALIZATION_PROPERTY, value);
+                setQueryProperty(ProcedureQueryDescriptor.COLUMN_NAME_CAPITALIZATION_PROPERTY, value);
             });
 
             PanelBuilder builder = super.createPanelBuilder();
@@ -313,9 +312,10 @@ public class ProcedureQueryView extends ProjectPanel {
 
             if (query != null && QueryDescriptor.PROCEDURE_QUERY.equals(query.getType())) {
                 DefaultComboBoxModel<CapsStrategy> labelCaseModel = new DefaultComboBoxModel<>(LABEL_CAPITALIZATION);
-                String columnNameCapitalization = query.getProperty(ProcedureQuery.COLUMN_NAME_CAPITALIZATION_PROPERTY);
+                ProcedureQueryDescriptor procedureQuery = (ProcedureQueryDescriptor) query;
+                CapsStrategy columnNameCapitalization = procedureQuery.getColumnNamesCapitalization();
                 labelCaseModel.setSelectedItem(columnNameCapitalization != null ?
-                        CapsStrategy.valueOf(columnNameCapitalization) : CapsStrategy.DEFAULT);
+                        columnNameCapitalization : CapsStrategy.DEFAULT);
                 labelCase.setModel(labelCaseModel);
             }
         }
diff --git a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ObjectQueryPropertiesPanel.java b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ObjectQueryPropertiesPanel.java
index 3bc6ccd85..407cc3512 100644
--- a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ObjectQueryPropertiesPanel.java
+++ b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/ObjectQueryPropertiesPanel.java
@@ -27,7 +27,6 @@ import org.apache.cayenne.modeler.toolkit.checkbox.CMCheckBox;
 import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.modeler.ui.project.editor.EditorForm;
-import org.apache.cayenne.query.QueryMetadata;
 
 import com.jgoodies.forms.builder.PanelBuilder;
 import com.jgoodies.forms.layout.CellConstraints;
@@ -80,7 +79,7 @@ public class ObjectQueryPropertiesPanel extends SelectPropertiesPanel {
 
         dataRows.addItemListener(e -> {
             Boolean b = dataRows.isSelected() ? Boolean.TRUE : Boolean.FALSE;
-            setQueryProperty(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY, String.valueOf(b));
+            setQueryProperty(QueryDescriptor.FETCHING_DATA_ROWS_PROPERTY, String.valueOf(b));
         });
     }
 
@@ -91,6 +90,6 @@ public class ObjectQueryPropertiesPanel extends SelectPropertiesPanel {
     public void initFromModel(QueryDescriptor query) {
         super.initFromModel(query);
 
-        dataRows.setSelected(Boolean.valueOf(query.getProperty(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY)));
+        dataRows.setSelected(query.isFetchingDataRows());
     }
 }
diff --git a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RawQueryPropertiesPanel.java b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RawQueryPropertiesPanel.java
index eee5afd5c..a340cf977 100644
--- a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RawQueryPropertiesPanel.java
+++ b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/RawQueryPropertiesPanel.java
@@ -40,7 +40,6 @@ import com.jgoodies.forms.layout.CellConstraints;
 import com.jgoodies.forms.layout.FormLayout;
 import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.modeler.ui.project.editor.EditorForm;
-import org.apache.cayenne.query.QueryMetadata;
 
 /**
  * A panel that supports editing the properties a query not based on ObjEntity, but still
@@ -112,7 +111,7 @@ public abstract class RawQueryPropertiesPanel extends SelectPropertiesPanel {
     public void initFromModel(QueryDescriptor query) {
         super.initFromModel(query);
 
-        boolean fetchingDO = !Boolean.valueOf(query.getProperties().get(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY));
+        boolean fetchingDO = !query.isFetchingDataRows();
         persistentObjects.setSelected(fetchingDO);
 
         // TODO: now we only allow ObjEntities from the current map,
@@ -143,7 +142,7 @@ public abstract class RawQueryPropertiesPanel extends SelectPropertiesPanel {
             entities.getModel().setSelectedItem(null);
         }
 
-        setQueryProperty(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY,
+        setQueryProperty(QueryDescriptor.FETCHING_DATA_ROWS_PROPERTY,
                 fetchingPersistentObjects ? Boolean.FALSE.toString() : Boolean.TRUE.toString());
     }
 }
diff --git a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/SelectPropertiesPanel.java b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/SelectPropertiesPanel.java
index 2e295a07a..a9f3298ca 100644
--- a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/SelectPropertiesPanel.java
+++ b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/SelectPropertiesPanel.java
@@ -37,7 +37,6 @@ import org.apache.cayenne.modeler.toolkit.text.CMUndoableTextField;
 import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.query.QueryCacheStrategy;
 import org.apache.cayenne.map.QueryDescriptor;
-import org.apache.cayenne.query.QueryMetadata;
 import java.util.Objects;
 import org.apache.cayenne.validation.ValidationException;
 
@@ -104,7 +103,7 @@ public abstract class SelectPropertiesPanel extends ProjectPanel {
     protected void initController() {
         cacheStrategy.addActionListener(event -> {
             QueryCacheStrategy strategy = (QueryCacheStrategy) cacheStrategy.getModel().getSelectedItem();
-            setQueryProperty(QueryMetadata.CACHE_STRATEGY_PROPERTY, strategy.name());
+            setQueryProperty(QueryDescriptor.CACHE_STRATEGY_PROPERTY, strategy.name());
             setCacheGroupsEnabled(strategy != QueryCacheStrategy.NO_CACHE);
         });
     }
@@ -116,35 +115,27 @@ public abstract class SelectPropertiesPanel extends ProjectPanel {
     public void initFromModel(QueryDescriptor query) {
         DefaultComboBoxModel<QueryCacheStrategy> cacheModel = new DefaultComboBoxModel<>(CACHE_POLICIES);
 
-        String selectedStrategyString = query.getProperty(QueryMetadata.CACHE_STRATEGY_PROPERTY);
-
-        QueryCacheStrategy selectedStrategy = selectedStrategyString != null ?
-                QueryCacheStrategy.valueOf(selectedStrategyString) : null;
-
-        cacheModel.setSelectedItem(selectedStrategy != null ?
-                selectedStrategy : QueryCacheStrategy.getDefaultStrategy());
-
+        QueryCacheStrategy selectedStrategy = query.getCacheStrategy();
+        cacheModel.setSelectedItem(selectedStrategy);
         cacheStrategy.setModel(cacheModel);
 
-        cacheGroups.setText(query.getProperty(QueryMetadata.CACHE_GROUPS_PROPERTY));
-        setCacheGroupsEnabled(selectedStrategy != null && selectedStrategy != QueryCacheStrategy.NO_CACHE);
-
-        String fetchOffsetStr = query.getProperty(QueryMetadata.FETCH_OFFSET_PROPERTY);
-        String fetchLimitStr = query.getProperty(QueryMetadata.FETCH_LIMIT_PROPERTY);
-        String pageSizeStr = query.getProperty(QueryMetadata.PAGE_SIZE_PROPERTY);
+        // showing the raw property rather than QueryDescriptor.getCacheGroup(), so that a legacy comma-separated
+        // list of groups stays visible (and fixable) to the user
+        cacheGroups.setText(query.getProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY));
+        setCacheGroupsEnabled(selectedStrategy != QueryCacheStrategy.NO_CACHE);
 
-        fetchOffset.setText(fetchOffsetStr != null ? fetchOffsetStr : ZERO.toString());
-        fetchLimit.setText(fetchLimitStr != null ? fetchLimitStr : ZERO.toString());
-        pageSize.setText(pageSizeStr != null ? pageSizeStr : ZERO.toString());
+        fetchOffset.setText(String.valueOf(query.getFetchOffset()));
+        fetchLimit.setText(String.valueOf(query.getFetchLimit()));
+        pageSize.setText(String.valueOf(query.getPageSize()));
     }
 
     void setFetchOffset(String string) {
         string = string == null ? "" : string.trim();
         if (string.length() == 0) {
-            setQueryProperty(QueryMetadata.FETCH_OFFSET_PROPERTY, ZERO.toString());
+            setQueryProperty(QueryDescriptor.FETCH_OFFSET_PROPERTY, ZERO.toString());
         } else {
             if (isNumeric(string)) {
-                setQueryProperty(QueryMetadata.FETCH_OFFSET_PROPERTY, string);
+                setQueryProperty(QueryDescriptor.FETCH_OFFSET_PROPERTY, string);
             } else {
                 throw new ValidationException("Fetch offset must be an integer: %s", string);
             }
@@ -154,10 +145,10 @@ public abstract class SelectPropertiesPanel extends ProjectPanel {
     void setFetchLimit(String string) {
         string = (string == null) ? "" : string.trim();
         if (string.length() == 0) {
-            setQueryProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, ZERO.toString());
+            setQueryProperty(QueryDescriptor.FETCH_LIMIT_PROPERTY, ZERO.toString());
         } else {
             if (isNumeric(string)) {
-                setQueryProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, string);
+                setQueryProperty(QueryDescriptor.FETCH_LIMIT_PROPERTY, string);
             } else {
                 throw new ValidationException("Fetch limit must be an integer: %s", string);
             }
@@ -167,10 +158,10 @@ public abstract class SelectPropertiesPanel extends ProjectPanel {
     void setPageSize(String string) {
         string = (string == null) ? "" : string.trim();
         if (string.length() == 0) {
-            setQueryProperty(QueryMetadata.PAGE_SIZE_PROPERTY, ZERO.toString());
+            setQueryProperty(QueryDescriptor.PAGE_SIZE_PROPERTY, ZERO.toString());
         } else {
             if (isNumeric(string)) {
-                setQueryProperty(QueryMetadata.PAGE_SIZE_PROPERTY, string);
+                setQueryProperty(QueryDescriptor.PAGE_SIZE_PROPERTY, string);
             } else {
                 throw new ValidationException("Page size must be an integer: %s", string);
             }
@@ -179,7 +170,7 @@ public abstract class SelectPropertiesPanel extends ProjectPanel {
 
     void setCacheGroups(String string) {
         string = (string == null) ? "" : string.trim();
-        setQueryProperty(QueryMetadata.CACHE_GROUPS_PROPERTY, string);
+        setQueryProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY, string);
     }
 
     QueryDescriptor getQuery() {
diff --git a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/selectquery/SelectQueryMainTab.java b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/selectquery/SelectQueryMainTab.java
index 2dec4af64..897440cb1 100644
--- a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/selectquery/SelectQueryMainTab.java
+++ b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/selectquery/SelectQueryMainTab.java
@@ -124,8 +124,8 @@ public class SelectQueryMainTab extends BaseQueryMainTab {
     private void initController() {
         distinct.addItemListener(e -> {
             QueryDescriptor query = getQuery();
-            if (query != null) {
-                query.setProperty(SelectQueryDescriptor.DISTINCT_PROPERTY, Boolean.toString(distinct.isSelected()));
+            if (query instanceof SelectQueryDescriptor selectQuery) {
+                selectQuery.setDistinct(distinct.isSelected());
                 session.fireQueryEvent(QueryEvent.ofChange(this, query));
             }
         });
@@ -146,7 +146,7 @@ public class SelectQueryMainTab extends BaseQueryMainTab {
         SelectQueryDescriptor query = (SelectQueryDescriptor) descriptor;
 
         name.setText(query.getName());
-        distinct.setSelected(Boolean.parseBoolean(query.getProperties().get(SelectQueryDescriptor.DISTINCT_PROPERTY)));
+        distinct.setSelected(query.isDistinct());
         qualifier.setText(query.getQualifier() != null ? query
                 .getQualifier()
                 .toString() : null);
diff --git a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/sqltemplate/SQLTemplateMainTab.java b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/sqltemplate/SQLTemplateMainTab.java
index db594fc44..23df502af 100644
--- a/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/sqltemplate/SQLTemplateMainTab.java
+++ b/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/project/editor/query/sqltemplate/SQLTemplateMainTab.java
@@ -32,13 +32,13 @@ import org.apache.cayenne.modeler.event.model.QueryEvent;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.map.SQLTemplateDescriptor;
 import org.apache.cayenne.modeler.toolkit.combobox.CMUndoableComboBox;
 import org.apache.cayenne.modeler.toolkit.text.CMUndoableTextField;
 import org.apache.cayenne.modeler.project.ProjectSession;
 import org.apache.cayenne.modeler.project.ProjectComparators;
 import org.apache.cayenne.project.extension.info.ObjectInfo;
 import org.apache.cayenne.query.CapsStrategy;
-import org.apache.cayenne.query.SQLTemplate;
 
 import javax.swing.DefaultComboBoxModel;
 import javax.swing.DefaultListCellRenderer;
@@ -223,7 +223,7 @@ public class SQLTemplateMainTab extends BaseQueryMainTab {
 
             labelCase.addActionListener(event -> {
                 CapsStrategy value = (CapsStrategy) labelCase.getModel().getSelectedItem();
-                setQueryProperty(SQLTemplate.COLUMN_NAME_CAPITALIZATION_PROPERTY, value.name());
+                setQueryProperty(SQLTemplateDescriptor.COLUMN_NAME_CAPITALIZATION_PROPERTY, value.name());
             });
 
             PanelBuilder builder = super.createPanelBuilder();
@@ -246,10 +246,9 @@ public class SQLTemplateMainTab extends BaseQueryMainTab {
 
             if (query != null && QueryDescriptor.SQL_TEMPLATE.equals(query.getType())) {
                 DefaultComboBoxModel<CapsStrategy> labelCaseModel = new DefaultComboBoxModel<>(LABEL_CAPITALIZATION);
-                String columnNameCapitalization = query.getProperty(SQLTemplate.COLUMN_NAME_CAPITALIZATION_PROPERTY);
-
+                CapsStrategy columnNameCapitalization = ((SQLTemplateDescriptor) query).getColumnNamesCapitalization();
                 labelCaseModel.setSelectedItem(columnNameCapitalization != null
-                        ? CapsStrategy.valueOf(columnNameCapitalization)
+                        ? columnNameCapitalization
                         : CapsStrategy.DEFAULT);
                 labelCase.setModel(labelCaseModel);
             }
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java
index 3c21ba8d8..1a7be0b2b 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/validation/BaseQueryValidator.java
@@ -21,7 +21,6 @@ package org.apache.cayenne.project.validation;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.QueryDescriptor;
-import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.validation.ValidationResult;
 
@@ -94,7 +93,7 @@ abstract class BaseQueryValidator<T extends QueryDescriptor> extends Configurati
     }
 
     void checkForMultiCacheGroup(T query, ValidationResult validationResult) {
-        String cacheGroup = query.getProperty(QueryMetadata.CACHE_GROUPS_PROPERTY);
+        String cacheGroup = query.getProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY);
         if (cacheGroup != null && cacheGroup.contains(",")) {
             addFailure(validationResult, query, "Invalid cache group '%s', multiple groups are deprecated",
                     cacheGroup);
diff --git a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOModelProcessor.java b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOModelProcessor.java
index 079ccf07b..3b158a9f7 100644
--- a/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOModelProcessor.java
+++ b/cayenne-wocompat/src/main/java/org/apache/cayenne/wocompat/EOModelProcessor.java
@@ -33,7 +33,6 @@ import org.apache.cayenne.map.SQLTemplateDescriptor;
 import org.apache.cayenne.map.SelectQueryDescriptor;
 import org.apache.cayenne.query.Ordering;
 import org.apache.cayenne.query.PrefetchTreeNode;
-import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.query.SortOrder;
 import org.apache.cayenne.wocompat.parser.Parser;
 import org.slf4j.Logger;
@@ -238,10 +237,9 @@ public class EOModelProcessor {
 		if (fetchLimit != null) {
 			try {
 				if (fetchLimit instanceof Number n) {
-					descriptor.setProperty(QueryMetadata.FETCH_LIMIT_PROPERTY,
-							String.valueOf(n.intValue()));
+					descriptor.setFetchLimit(n.intValue());
 				} else if (isNumeric(fetchLimit.toString())) {
-					descriptor.setProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, fetchLimit.toString());
+					descriptor.setFetchLimit(Integer.parseInt(fetchLimit.toString()));
 				}
 			} catch (NumberFormatException nfex) {
 				// ignoring...
@@ -279,7 +277,7 @@ public class EOModelProcessor {
 		// in the
 		// modeler...
 		if (plistMap.containsKey("rawRowKeyPaths")) {
-			descriptor.setProperty(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY, String.valueOf(true));
+			descriptor.setFetchingDataRows(true);
 		}
 
 		return descriptor;
@@ -293,10 +291,9 @@ public class EOModelProcessor {
 		if (fetchLimit != null) {
 			try {
 				if (fetchLimit instanceof Number n) {
-					descriptor.setProperty(QueryMetadata.FETCH_LIMIT_PROPERTY,
-							String.valueOf(n.intValue()));
+					descriptor.setFetchLimit(n.intValue());
 				} else if (isNumeric(fetchLimit.toString())) {
-					descriptor.setProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, fetchLimit.toString());
+					descriptor.setFetchLimit(Integer.parseInt(fetchLimit.toString()));
 				}
 			} catch (NumberFormatException nfex) {
 				// ignoring...
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java
index 367495576..e769a4ea8 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java
@@ -52,7 +52,13 @@ public class EJBQLQueryDescriptor extends QueryDescriptor {
     @Override
     public EJBQLQuery<?> buildQuery() {
         EJBQLQuery<?> ejbqlQuery = new EJBQLQuery<>(this.getEjbql());
-        ejbqlQuery.initWithProperties(this.getProperties());
+        ejbqlQuery.setFetchLimit(getFetchLimit());
+        ejbqlQuery.setFetchOffset(getFetchOffset());
+        ejbqlQuery.setPageSize(getPageSize());
+        ejbqlQuery.setStatementFetchSize(getStatementFetchSize());
+        ejbqlQuery.setFetchingDataRows(isFetchingDataRows());
+        ejbqlQuery.setCacheStrategy(getCacheStrategy());
+        ejbqlQuery.setCacheGroup(getCacheGroup());
 
         return ejbqlQuery;
     }
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java
index d4d784a6b..acac93e63 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.map;
 
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.query.CapsStrategy;
 import org.apache.cayenne.query.ProcedureQuery;
 import org.apache.cayenne.util.XMLEncoder;
 
@@ -29,6 +30,14 @@ import java.util.Map;
  */
 public class ProcedureQueryDescriptor extends QueryDescriptor {
 
+    /**
+     * Name of the descriptor property holding the {@link #getColumnNamesCapitalization() column name
+     * capitalization} of the query.
+     *
+     * @since 5.0
+     */
+    public static final String COLUMN_NAME_CAPITALIZATION_PROPERTY = "cayenne.ProcedureQuery.columnNameCapitalization";
+
     protected String resultEntityName;
 
     public ProcedureQueryDescriptor() {
@@ -49,6 +58,24 @@ public class ProcedureQueryDescriptor extends QueryDescriptor {
         this.resultEntityName = resultEntityName;
     }
 
+    /**
+     * Returns the capitalization strategy applied to the column names of the query result, or null if none is set.
+     *
+     * @since 5.0
+     */
+    public CapsStrategy getColumnNamesCapitalization() {
+        String value = getProperty(COLUMN_NAME_CAPITALIZATION_PROPERTY);
+        return value != null ? CapsStrategy.valueOf(value.toUpperCase()) : null;
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setColumnNamesCapitalization(CapsStrategy columnNamesCapitalization) {
+        setProperty(COLUMN_NAME_CAPITALIZATION_PROPERTY,
+                columnNamesCapitalization != null ? columnNamesCapitalization.name() : null);
+    }
+
     @Override
     public ProcedureQuery<?> buildQuery() {
         ProcedureQuery<?> procedureQuery = new ProcedureQuery<>();
@@ -58,7 +85,14 @@ public class ProcedureQueryDescriptor extends QueryDescriptor {
         }
 
         procedureQuery.setResultEntityName(this.getResultEntityName());
-        procedureQuery.initWithProperties(this.getProperties());
+        procedureQuery.setFetchLimit(getFetchLimit());
+        procedureQuery.setFetchOffset(getFetchOffset());
+        procedureQuery.setPageSize(getPageSize());
+        procedureQuery.setStatementFetchSize(getStatementFetchSize());
+        procedureQuery.setFetchingDataRows(isFetchingDataRows());
+        procedureQuery.setCacheStrategy(getCacheStrategy());
+        procedureQuery.setCacheGroup(getCacheGroup());
+        procedureQuery.setColumnNamesCapitalization(getColumnNamesCapitalization());
 
         return procedureQuery;
     }
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/QueryDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/map/QueryDescriptor.java
index 63343d272..e58bb8ee5 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/QueryDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/QueryDescriptor.java
@@ -22,6 +22,8 @@ import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.QueryCacheStrategy;
+import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
 
@@ -65,6 +67,56 @@ public class QueryDescriptor implements ConfigurationNode, XMLSerializable {
      */
     public static final String JAVA_CLASS_ROOT = "java-class";
 
+    /**
+     * Name of the descriptor property holding the query {@link #getFetchLimit() fetch limit}.
+     *
+     * @since 5.0
+     */
+    public static final String FETCH_LIMIT_PROPERTY = "cayenne.GenericSelectQuery.fetchLimit";
+
+    /**
+     * Name of the descriptor property holding the query {@link #getFetchOffset() fetch offset}.
+     *
+     * @since 5.0
+     */
+    public static final String FETCH_OFFSET_PROPERTY = "cayenne.GenericSelectQuery.fetchOffset";
+
+    /**
+     * Name of the descriptor property holding the query {@link #getPageSize() page size}.
+     *
+     * @since 5.0
+     */
+    public static final String PAGE_SIZE_PROPERTY = "cayenne.GenericSelectQuery.pageSize";
+
+    /**
+     * Name of the descriptor property holding the query {@link #isFetchingDataRows() data rows} flag.
+     *
+     * @since 5.0
+     */
+    public static final String FETCHING_DATA_ROWS_PROPERTY = "cayenne.GenericSelectQuery.fetchingDataRows";
+
+    /**
+     * Name of the descriptor property holding the query {@link #getCacheStrategy() cache strategy}.
+     *
+     * @since 5.0
+     */
+    public static final String CACHE_STRATEGY_PROPERTY = "cayenne.GenericSelectQuery.cacheStrategy";
+
+    /**
+     * Name of the descriptor property holding the query {@link #getCacheGroup() cache group}. The plural name is
+     * historical: older projects could store a comma-separated list of groups under this property.
+     *
+     * @since 5.0
+     */
+    public static final String CACHE_GROUPS_PROPERTY = "cayenne.GenericSelectQuery.cacheGroups";
+
+    /**
+     * Name of the descriptor property holding the query {@link #getStatementFetchSize() statement fetch size}.
+     *
+     * @since 5.0
+     */
+    public static final String STATEMENT_FETCH_SIZE_PROPERTY = "cayenne.GenericSelectQuery.statementFetchSize";
+
     /**
      * Creates new SelectQuery query descriptor.
      */
@@ -173,7 +225,9 @@ public class QueryDescriptor implements ConfigurationNode, XMLSerializable {
     }
 
     /**
-     * Returns map of query properties set up for this query.
+     * Returns map of query properties set up for this query. Properties are the raw String values stored in the
+     * project XML. Typed accessors like {@link #getFetchLimit()} or {@link #getCacheStrategy()} interpret the
+     * well-known properties.
      */
     public Map<String, String> getProperties() {
         return properties;
@@ -194,10 +248,148 @@ public class QueryDescriptor implements ConfigurationNode, XMLSerializable {
     }
 
     /**
-     * Sets single query property.
+     * Sets single query property. A null value removes the property.
      */
     public void setProperty(String name, String value) {
-        this.properties.put(name, value);
+        if (value == null) {
+            this.properties.remove(name);
+        } else {
+            this.properties.put(name, value);
+        }
+    }
+
+    /**
+     * Returns the fetch limit of the query, or {@link QueryMetadata#FETCH_LIMIT_DEFAULT} if it is not set.
+     *
+     * @since 5.0
+     */
+    public int getFetchLimit() {
+        return intProperty(FETCH_LIMIT_PROPERTY, QueryMetadata.FETCH_LIMIT_DEFAULT);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setFetchLimit(int fetchLimit) {
+        setProperty(FETCH_LIMIT_PROPERTY, String.valueOf(fetchLimit));
+    }
+
+    /**
+     * Returns the fetch offset of the query, or {@link QueryMetadata#FETCH_OFFSET_DEFAULT} if it is not set.
+     *
+     * @since 5.0
+     */
+    public int getFetchOffset() {
+        return intProperty(FETCH_OFFSET_PROPERTY, QueryMetadata.FETCH_OFFSET_DEFAULT);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setFetchOffset(int fetchOffset) {
+        setProperty(FETCH_OFFSET_PROPERTY, String.valueOf(fetchOffset));
+    }
+
+    /**
+     * Returns the page size of the query, or {@link QueryMetadata#PAGE_SIZE_DEFAULT} if it is not set.
+     *
+     * @since 5.0
+     */
+    public int getPageSize() {
+        return intProperty(PAGE_SIZE_PROPERTY, QueryMetadata.PAGE_SIZE_DEFAULT);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setPageSize(int pageSize) {
+        setProperty(PAGE_SIZE_PROPERTY, String.valueOf(pageSize));
+    }
+
+    /**
+     * Returns the JDBC statement fetch size of the query, or {@link QueryMetadata#STATEMENT_FETCH_SIZE_DEFAULT} if
+     * it is not set.
+     *
+     * @since 5.0
+     */
+    public int getStatementFetchSize() {
+        return intProperty(STATEMENT_FETCH_SIZE_PROPERTY, QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT);
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setStatementFetchSize(int statementFetchSize) {
+        setProperty(STATEMENT_FETCH_SIZE_PROPERTY, String.valueOf(statementFetchSize));
+    }
+
+    /**
+     * Returns whether the query fetches DataRows instead of Persistent objects. Defaults to
+     * {@link QueryMetadata#FETCHING_DATA_ROWS_DEFAULT} if the property is not set.
+     *
+     * @since 5.0
+     */
+    public boolean isFetchingDataRows() {
+        String value = getProperty(FETCHING_DATA_ROWS_PROPERTY);
+        return value != null ? Boolean.parseBoolean(value) : QueryMetadata.FETCHING_DATA_ROWS_DEFAULT;
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setFetchingDataRows(boolean fetchingDataRows) {
+        setProperty(FETCHING_DATA_ROWS_PROPERTY, String.valueOf(fetchingDataRows));
+    }
+
+    /**
+     * Returns the cache strategy of the query. Defaults to {@link QueryCacheStrategy#getDefaultStrategy()} if the
+     * property is not set or holds an unknown value.
+     *
+     * @since 5.0
+     */
+    public QueryCacheStrategy getCacheStrategy() {
+        String value = getProperty(CACHE_STRATEGY_PROPERTY);
+        return value != null ? QueryCacheStrategy.safeValueOf(value) : QueryCacheStrategy.getDefaultStrategy();
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setCacheStrategy(QueryCacheStrategy cacheStrategy) {
+        setProperty(CACHE_STRATEGY_PROPERTY, cacheStrategy != null ? cacheStrategy.name() : null);
+    }
+
+    /**
+     * Returns the cache group of the query, or null if none is set. If the underlying property holds a legacy
+     * comma-separated list of groups, only the first non-empty group is returned.
+     *
+     * @since 5.0
+     */
+    public String getCacheGroup() {
+        String value = getProperty(CACHE_GROUPS_PROPERTY);
+        if (value == null) {
+            return null;
+        }
+
+        for (String group : value.split(",")) {
+            if (!group.isEmpty()) {
+                return group;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setCacheGroup(String cacheGroup) {
+        setProperty(CACHE_GROUPS_PROPERTY, cacheGroup);
+    }
+
+    private int intProperty(String name, int defaultValue) {
+        String value = getProperty(name);
+        return value != null ? Integer.parseInt(value) : defaultValue;
     }
 
     /**
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
index d6be245b8..68a49e8ec 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.map;
 
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.query.CapsStrategy;
 import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.query.SQLTemplate;
 import org.apache.cayenne.util.XMLEncoder;
@@ -32,6 +33,14 @@ import java.util.TreeSet;
  */
 public class SQLTemplateDescriptor extends QueryDescriptor {
 
+    /**
+     * Name of the descriptor property holding the {@link #getColumnNamesCapitalization() column name
+     * capitalization} of the query.
+     *
+     * @since 5.0
+     */
+    public static final String COLUMN_NAME_CAPITALIZATION_PROPERTY = "cayenne.SQLTemplate.columnNameCapitalization";
+
     protected String sql;
     protected Map<String, Integer> prefetchesMap = new HashMap<>();
 
@@ -69,6 +78,24 @@ public class SQLTemplateDescriptor extends QueryDescriptor {
         this.adapterSql = adapterSql;
     }
 
+    /**
+     * Returns the capitalization strategy applied to the column names of the query result, or null if none is set.
+     *
+     * @since 5.0
+     */
+    public CapsStrategy getColumnNamesCapitalization() {
+        String value = getProperty(COLUMN_NAME_CAPITALIZATION_PROPERTY);
+        return value != null ? CapsStrategy.valueOf(value.toUpperCase()) : null;
+    }
+
+    /**
+     * @since 5.0
+     */
+    public void setColumnNamesCapitalization(CapsStrategy columnNamesCapitalization) {
+        setProperty(COLUMN_NAME_CAPITALIZATION_PROPERTY,
+                columnNamesCapitalization != null ? columnNamesCapitalization.name() : null);
+    }
+
     /**
      * Returns map of prefetch paths with semantics for this query.
      * @since 4.1
@@ -113,7 +140,14 @@ public class SQLTemplateDescriptor extends QueryDescriptor {
             }
         }
 
-        template.initWithProperties(this.getProperties());
+        template.setFetchLimit(getFetchLimit());
+        template.setFetchOffset(getFetchOffset());
+        template.setPageSize(getPageSize());
+        template.setStatementFetchSize(getStatementFetchSize());
+        template.setFetchingDataRows(isFetchingDataRows());
+        template.setCacheStrategy(getCacheStrategy());
+        template.setCacheGroup(getCacheGroup());
+        template.setColumnNamesCapitalization(getColumnNamesCapitalization());
 
         // init SQL
         template.setDefaultTemplate(this.getSql());
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/SelectQueryDescriptor.java b/cayenne/src/main/java/org/apache/cayenne/map/SelectQueryDescriptor.java
index 5bc2ffb02..a4d623e61 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/SelectQueryDescriptor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/SelectQueryDescriptor.java
@@ -171,10 +171,20 @@ public class SelectQueryDescriptor extends QueryDescriptor {
             prefetchesMap.forEach(query::prefetch);
         }
 
-        query.initWithProperties(this.getProperties());
-        if(this.isDistinct()) {
+        query.limit(getFetchLimit())
+                .offset(getFetchOffset())
+                .pageSize(getPageSize())
+                .statementFetchSize(getStatementFetchSize())
+                .cacheStrategy(getCacheStrategy(), getCacheGroup());
+
+        if (isFetchingDataRows()) {
+            query.fetchDataRows();
+        }
+
+        if (isDistinct()) {
             query.distinct();
         }
+
         return query;
     }
 
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java b/cayenne/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
index 34006521b..4d5121623 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
@@ -23,7 +23,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.map.DataMap;
@@ -141,55 +140,6 @@ class BaseQueryMetadata implements QueryMetadata {
 		return false;
 	}
 
-	void initWithProperties(Map<String, ?> properties) {
-		// must init defaults even if properties are empty
-		if (properties == null) {
-			properties = Collections.emptyMap();
-		}
-
-		Object fetchOffset = properties.get(QueryMetadata.FETCH_OFFSET_PROPERTY);
-		Object fetchLimit = properties.get(QueryMetadata.FETCH_LIMIT_PROPERTY);
-		Object pageSize = properties.get(QueryMetadata.PAGE_SIZE_PROPERTY);
-		Object statementFetchSize = properties.get(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY);
-		Object fetchingDataRows = properties.get(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY);
-
-		Object cacheStrategy = properties.get(QueryMetadata.CACHE_STRATEGY_PROPERTY);
-
-		Object cacheGroups = properties.get(QueryMetadata.CACHE_GROUPS_PROPERTY);
-
-		// init ivars from properties
-		this.fetchOffset = (fetchOffset != null) ? Integer.parseInt(fetchOffset.toString())
-				: QueryMetadata.FETCH_OFFSET_DEFAULT;
-
-		this.fetchLimit = (fetchLimit != null) ? Integer.parseInt(fetchLimit.toString())
-				: QueryMetadata.FETCH_LIMIT_DEFAULT;
-
-		this.pageSize = (pageSize != null) ? Integer.parseInt(pageSize.toString()) : QueryMetadata.PAGE_SIZE_DEFAULT;
-
-		this.statementFetchSize = (statementFetchSize != null) ? Integer.parseInt(statementFetchSize.toString())
-				: QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT;
-
-		this.fetchingDataRows = (fetchingDataRows != null) ? "true".equalsIgnoreCase(fetchingDataRows.toString())
-				: QueryMetadata.FETCHING_DATA_ROWS_DEFAULT;
-
-		this.cacheStrategy = (cacheStrategy != null) ? QueryCacheStrategy.safeValueOf(cacheStrategy.toString())
-				: QueryCacheStrategy.getDefaultStrategy();
-
-		this.cacheGroup = null;
-		if(cacheGroups instanceof String) {
-			if(((String) cacheGroups).contains(",")) {
-				StringTokenizer toks = new StringTokenizer(cacheGroups.toString(), ",");
-				if(toks.countTokens() > 0) {
-					this.cacheGroup = toks.nextToken();
-				}
-			} else {
-				this.cacheGroup = (String) cacheGroups;
-			}
-		} else if (cacheGroups instanceof String[]) {
-			this.cacheGroup = ((String[]) cacheGroups)[0];
-		}
-	}
-
 	/**
 	 * @since 1.2
 	 */
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/EJBQLQuery.java b/cayenne/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
index feb72566d..d5fc4a117 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
@@ -53,15 +53,6 @@ public class EJBQLQuery<T> extends CacheableQuery implements Select<T> {
     public EJBQLQuery() {
     }
 
-    public void initWithProperties(Map<String, ?> properties) {
-
-        // must init defaults even if properties are empty
-        if (properties == null) {
-            properties = Collections.emptyMap();
-        }
-        metadata.initWithProperties(properties);
-    }
-
     public QueryMetadata getMetaData(EntityResolver resolver) {
         metadata.resolve(resolver, this);
         return metadata;
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/FluentSelect.java b/cayenne/src/main/java/org/apache/cayenne/query/FluentSelect.java
index 7bb590baa..8e4c808e3 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/FluentSelect.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/FluentSelect.java
@@ -710,13 +710,6 @@ public abstract class FluentSelect<T, S extends FluentSelect<T, S>> implements S
         return distinct;
     }
 
-    /**
-     * @since 4.2
-     */
-    public void initWithProperties(Map<String, String> properties) {
-        getBaseMetaData().initWithProperties(properties);
-    }
-
     /**
      * Utility method to perform (re)cast this type, doesn't perform any checks, so use with caution.
      *
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/ProcedureQuery.java b/cayenne/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
index 4ae59db0c..810deed85 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
@@ -59,8 +59,6 @@ import java.util.Map;
  */
 public class ProcedureQuery<T> extends CacheableQuery implements Select<T> {
 
-    public static final String COLUMN_NAME_CAPITALIZATION_PROPERTY = "cayenne.ProcedureQuery.columnNameCapitalization";
-
     protected String resultEntityName;
     protected Class<?> resultClass;
     protected CapsStrategy columnNamesCapitalization;
@@ -245,27 +243,6 @@ public class ProcedureQuery<T> extends CacheableQuery implements Select<T> {
         return context.selectFirst(this);
     }
 
-    /**
-     * Initializes query parameters using a set of properties.
-     * 
-     * @since 1.1
-     */
-    public void initWithProperties(Map<String, ?> properties) {
-
-        // must init defaults even if properties are empty
-        if (properties == null) {
-            properties = Collections.emptyMap();
-        }
-        Object columnNamesCapitalization = properties
-                .get(COLUMN_NAME_CAPITALIZATION_PROPERTY);
-        this.columnNamesCapitalization = (columnNamesCapitalization != null)
-                ? CapsStrategy
-                        .valueOf(columnNamesCapitalization.toString().toUpperCase())
-                : null;
-
-        metaData.initWithProperties(properties);
-    }
-
     @Override
     protected BaseQueryMetadata getBaseMetaData() {
         return metaData;
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/QueryMetadata.java b/cayenne/src/main/java/org/apache/cayenne/query/QueryMetadata.java
index 262c24b85..2f861f37d 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/QueryMetadata.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/QueryMetadata.java
@@ -36,26 +36,12 @@ import org.apache.cayenne.reflect.ClassDescriptor;
  */
 public interface QueryMetadata {
 
-    /**
-     * Defines the name of the property for the query {@link #getFetchLimit() fetch limit}
-     * .
-     */
-    String FETCH_LIMIT_PROPERTY = "cayenne.GenericSelectQuery.fetchLimit";
-
     /**
      * Defines default query fetch limit, which is zero, meaning that all matching rows
      * should be fetched.
      */
     int FETCH_LIMIT_DEFAULT = 0;
 
-    /**
-     * Defines the name of the property for the query {@link #getFetchOffset() fetch
-     * offset}.
-     * 
-     * @since 3.0
-     */
-    String FETCH_OFFSET_PROPERTY = "cayenne.GenericSelectQuery.fetchOffset";
-
     /**
      * Defines default query fetch start index, which is 0, meaning that matching rows
      * selected starting from the first.
@@ -66,41 +52,15 @@ public interface QueryMetadata {
 
     int QUERY_TIMEOUT_DEFAULT = -1;
 
-    /**
-     * Defines the name of the property for the query {@link #getPageSize() page size}.
-     */
-    String PAGE_SIZE_PROPERTY = "cayenne.GenericSelectQuery.pageSize";
-
     /**
      * Defines default query page size, which is zero for no pagination.
      */
     int PAGE_SIZE_DEFAULT = 0;
 
-    String FETCHING_DATA_ROWS_PROPERTY = "cayenne.GenericSelectQuery.fetchingDataRows";
-
     boolean FETCHING_DATA_ROWS_DEFAULT = false;
 
     /**
-     * @since 3.0
-     */
-    String CACHE_STRATEGY_PROPERTY = "cayenne.GenericSelectQuery.cacheStrategy";
-
-    /**
-     * @since 3.0
-     */
-    String CACHE_GROUPS_PROPERTY = "cayenne.GenericSelectQuery.cacheGroups";
-
-    /**
-     * Defines the name of the property for the query {@link #getStatementFetchSize() fetch
-     * size}.
-     * 
-     * @since 3.0
-     */
-    String STATEMENT_FETCH_SIZE_PROPERTY = "cayenne.GenericSelectQuery.statementFetchSize";
-
-    /**
-     * Defines default query fetch start index, which is 0, meaning that matching rows
-     * selected starting from the first.
+     * Defines default statement fetch size, which is 0, meaning that the JDBC driver default is used.
      * 
      * @since 3.0
      */
diff --git a/cayenne/src/main/java/org/apache/cayenne/query/SQLTemplate.java b/cayenne/src/main/java/org/apache/cayenne/query/SQLTemplate.java
index 80a50c2f4..2ae095919 100644
--- a/cayenne/src/main/java/org/apache/cayenne/query/SQLTemplate.java
+++ b/cayenne/src/main/java/org/apache/cayenne/query/SQLTemplate.java
@@ -72,8 +72,6 @@ import java.util.stream.Stream;
  */
 public class SQLTemplate<T> extends CacheableQuery implements Select<T> {
 
-	public static final String COLUMN_NAME_CAPITALIZATION_PROPERTY = "cayenne.SQLTemplate.columnNameCapitalization";
-
 	private static final Function<Map<String, ?>, Map<String, ?>> nullMapTransformer = input ->
 			(input != null) ? input : Collections.emptyMap();
 
@@ -245,24 +243,6 @@ public class SQLTemplate<T> extends CacheableQuery implements Select<T> {
 		return context.selectFirst(this);
 	}
 
-	/**
-	 * Initializes query parameters using a set of properties.
-	 * 
-	 * @since 1.1
-	 */
-	public void initWithProperties(Map<String, ?> properties) {
-		// must init defaults even if properties are empty
-		metaData.initWithProperties(properties);
-
-		if (properties == null) {
-			properties = Collections.emptyMap();
-		}
-
-		Object columnNamesCapitalization = properties.get(COLUMN_NAME_CAPITALIZATION_PROPERTY);
-		this.columnNamesCapitalization = (columnNamesCapitalization != null) ? CapsStrategy
-				.valueOf(columnNamesCapitalization.toString().toUpperCase()) : null;
-	}
-
 	/**
 	 * Returns an iterator over parameter sets. Each element returned from the
 	 * iterator is a java.util.Map.
diff --git a/cayenne/src/test/java/org/apache/cayenne/map/EJBQLQueryDescriptorTest.java b/cayenne/src/test/java/org/apache/cayenne/map/EJBQLQueryDescriptorTest.java
index a878d02fe..c4f465673 100644
--- a/cayenne/src/test/java/org/apache/cayenne/map/EJBQLQueryDescriptorTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/map/EJBQLQueryDescriptorTest.java
@@ -22,9 +22,11 @@ package org.apache.cayenne.map;
 import java.util.Map;
 
 import org.apache.cayenne.query.EJBQLQuery;
+import org.apache.cayenne.query.QueryCacheStrategy;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class EJBQLQueryDescriptorTest {
 
@@ -37,4 +39,27 @@ public class EJBQLQueryDescriptorTest {
 
         assertEquals(Map.of("name", "artist1"), query.getNamedParameters());
     }
+
+    @Test
+    public void getQueryProperties() {
+        EJBQLQueryDescriptor builder = QueryDescriptor.ejbqlQueryDescriptor();
+        builder.setEjbql("select a from Artist a");
+        builder.setProperty(QueryDescriptor.FETCH_LIMIT_PROPERTY, "5");
+        builder.setProperty(QueryDescriptor.FETCH_OFFSET_PROPERTY, "2");
+        builder.setProperty(QueryDescriptor.PAGE_SIZE_PROPERTY, "10");
+        builder.setProperty(QueryDescriptor.STATEMENT_FETCH_SIZE_PROPERTY, "6");
+        builder.setProperty(QueryDescriptor.FETCHING_DATA_ROWS_PROPERTY, "true");
+        builder.setProperty(QueryDescriptor.CACHE_STRATEGY_PROPERTY, "SHARED_CACHE");
+        builder.setProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY, "g1");
+
+        EJBQLQuery<?> query = builder.buildQuery();
+        assertEquals("select a from Artist a", query.getEjbqlStatement());
+        assertEquals(5, query.getFetchLimit());
+        assertEquals(2, query.getFetchOffset());
+        assertEquals(10, query.getPageSize());
+        assertEquals(6, query.getStatementFetchSize());
+        assertTrue(query.isFetchingDataRows());
+        assertEquals(QueryCacheStrategy.SHARED_CACHE, query.getCacheStrategy());
+        assertEquals("g1", query.getCacheGroup());
+    }
 }
diff --git a/cayenne/src/test/java/org/apache/cayenne/map/ProcedureQueryDescriptorTest.java b/cayenne/src/test/java/org/apache/cayenne/map/ProcedureQueryDescriptorTest.java
index f0dc3f611..4d4adb65f 100644
--- a/cayenne/src/test/java/org/apache/cayenne/map/ProcedureQueryDescriptorTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/map/ProcedureQueryDescriptorTest.java
@@ -21,10 +21,15 @@ package org.apache.cayenne.map;
 
 import java.util.Map;
 
+import org.apache.cayenne.query.CapsStrategy;
 import org.apache.cayenne.query.ProcedureQuery;
+import org.apache.cayenne.query.QueryCacheStrategy;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class ProcedureQueryDescriptorTest {
 
@@ -38,4 +43,53 @@ public class ProcedureQueryDescriptorTest {
 
         assertEquals(params, query.getParameters());
     }
+
+    @Test
+    public void getQueryRoot() {
+        Procedure procedure = new Procedure("p1");
+
+        ProcedureQueryDescriptor builder = QueryDescriptor.procedureQueryDescriptor();
+        builder.setRoot(procedure);
+        builder.setResultEntityName("A");
+
+        ProcedureQuery<?> query = builder.buildQuery();
+        assertSame(procedure, query.getRoot());
+        assertEquals("A", query.getResultEntityName());
+    }
+
+    @Test
+    public void getQueryProperties() {
+        ProcedureQueryDescriptor builder = QueryDescriptor.procedureQueryDescriptor();
+        builder.setProperty(QueryDescriptor.FETCH_LIMIT_PROPERTY, "5");
+        builder.setProperty(QueryDescriptor.FETCH_OFFSET_PROPERTY, "2");
+        builder.setProperty(QueryDescriptor.PAGE_SIZE_PROPERTY, "10");
+        builder.setProperty(QueryDescriptor.STATEMENT_FETCH_SIZE_PROPERTY, "6");
+        builder.setProperty(QueryDescriptor.FETCHING_DATA_ROWS_PROPERTY, "true");
+        builder.setProperty(QueryDescriptor.CACHE_STRATEGY_PROPERTY, "LOCAL_CACHE");
+        builder.setProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY, "g1");
+        builder.setProperty(ProcedureQueryDescriptor.COLUMN_NAME_CAPITALIZATION_PROPERTY, "UPPER");
+
+        ProcedureQuery<?> query = builder.buildQuery();
+        assertEquals(5, query.getFetchLimit());
+        assertEquals(2, query.getFetchOffset());
+        assertEquals(10, query.getPageSize());
+        assertEquals(6, query.getStatementFetchSize());
+        assertTrue(query.isFetchingDataRows());
+        assertEquals(QueryCacheStrategy.LOCAL_CACHE, query.getCacheStrategy());
+        assertEquals("g1", query.getCacheGroup());
+        assertEquals(CapsStrategy.UPPER, query.getColumnNamesCapitalization());
+    }
+
+    @Test
+    public void columnNamesCapitalization() {
+        ProcedureQueryDescriptor builder = QueryDescriptor.procedureQueryDescriptor();
+        assertNull(builder.getColumnNamesCapitalization());
+
+        builder.setColumnNamesCapitalization(CapsStrategy.LOWER);
+        assertEquals("LOWER", builder.getProperty(ProcedureQueryDescriptor.COLUMN_NAME_CAPITALIZATION_PROPERTY));
+        assertEquals(CapsStrategy.LOWER, builder.buildQuery().getColumnNamesCapitalization());
+
+        builder.setColumnNamesCapitalization(null);
+        assertNull(builder.getProperty(ProcedureQueryDescriptor.COLUMN_NAME_CAPITALIZATION_PROPERTY));
+    }
 }
diff --git a/cayenne/src/test/java/org/apache/cayenne/map/QueryDescriptorTest.java b/cayenne/src/test/java/org/apache/cayenne/map/QueryDescriptorTest.java
new file mode 100644
index 000000000..6d693a737
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/map/QueryDescriptorTest.java
@@ -0,0 +1,107 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.map;
+
+import org.apache.cayenne.query.QueryCacheStrategy;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class QueryDescriptorTest {
+
+    @Test
+    public void defaults() {
+        QueryDescriptor descriptor = QueryDescriptor.descriptor("Unknown");
+        assertEquals(0, descriptor.getFetchLimit());
+        assertEquals(0, descriptor.getFetchOffset());
+        assertEquals(0, descriptor.getPageSize());
+        assertEquals(0, descriptor.getStatementFetchSize());
+        assertFalse(descriptor.isFetchingDataRows());
+        assertEquals(QueryCacheStrategy.NO_CACHE, descriptor.getCacheStrategy());
+        assertNull(descriptor.getCacheGroup());
+    }
+
+    @Test
+    public void typedSettersStoreProperties() {
+        QueryDescriptor descriptor = QueryDescriptor.descriptor("Unknown");
+        descriptor.setFetchLimit(5);
+        descriptor.setFetchOffset(2);
+        descriptor.setPageSize(10);
+        descriptor.setStatementFetchSize(6);
+        descriptor.setFetchingDataRows(true);
+        descriptor.setCacheStrategy(QueryCacheStrategy.SHARED_CACHE);
+        descriptor.setCacheGroup("g1");
+
+        assertEquals("5", descriptor.getProperty(QueryDescriptor.FETCH_LIMIT_PROPERTY));
+        assertEquals("2", descriptor.getProperty(QueryDescriptor.FETCH_OFFSET_PROPERTY));
+        assertEquals("10", descriptor.getProperty(QueryDescriptor.PAGE_SIZE_PROPERTY));
+        assertEquals("6", descriptor.getProperty(QueryDescriptor.STATEMENT_FETCH_SIZE_PROPERTY));
+        assertEquals("true", descriptor.getProperty(QueryDescriptor.FETCHING_DATA_ROWS_PROPERTY));
+        assertEquals("SHARED_CACHE", descriptor.getProperty(QueryDescriptor.CACHE_STRATEGY_PROPERTY));
+        assertEquals("g1", descriptor.getProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY));
+
+        assertEquals(5, descriptor.getFetchLimit());
+        assertEquals(2, descriptor.getFetchOffset());
+        assertEquals(10, descriptor.getPageSize());
+        assertEquals(6, descriptor.getStatementFetchSize());
+        assertTrue(descriptor.isFetchingDataRows());
+        assertEquals(QueryCacheStrategy.SHARED_CACHE, descriptor.getCacheStrategy());
+        assertEquals("g1", descriptor.getCacheGroup());
+    }
+
+    @Test
+    public void nullRemovesProperty() {
+        QueryDescriptor descriptor = QueryDescriptor.descriptor("Unknown");
+        descriptor.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+        descriptor.setCacheGroup("g1");
+
+        descriptor.setCacheStrategy(null);
+        descriptor.setCacheGroup(null);
+
+        assertFalse(descriptor.getProperties().containsKey(QueryDescriptor.CACHE_STRATEGY_PROPERTY));
+        assertFalse(descriptor.getProperties().containsKey(QueryDescriptor.CACHE_GROUPS_PROPERTY));
+        assertEquals(QueryCacheStrategy.NO_CACHE, descriptor.getCacheStrategy());
+        assertNull(descriptor.getCacheGroup());
+    }
+
+    @Test
+    public void unknownCacheStrategyFallsBackToDefault() {
+        QueryDescriptor descriptor = QueryDescriptor.descriptor("Unknown");
+        descriptor.setProperty(QueryDescriptor.CACHE_STRATEGY_PROPERTY, "NO_SUCH_STRATEGY");
+        assertEquals(QueryCacheStrategy.NO_CACHE, descriptor.getCacheStrategy());
+    }
+
+    @Test
+    public void legacyCacheGroupList() {
+        QueryDescriptor descriptor = QueryDescriptor.descriptor("Unknown");
+
+        descriptor.setProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY, "g1,g2");
+        assertEquals("g1", descriptor.getCacheGroup());
+
+        descriptor.setProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY, ",g2");
+        assertEquals("g2", descriptor.getCacheGroup());
+
+        descriptor.setProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY, "");
+        assertNull(descriptor.getCacheGroup());
+    }
+}
diff --git a/cayenne/src/test/java/org/apache/cayenne/map/SQLTemplateDescriptorTest.java b/cayenne/src/test/java/org/apache/cayenne/map/SQLTemplateDescriptorTest.java
index 9710442b5..6a2b9044d 100644
--- a/cayenne/src/test/java/org/apache/cayenne/map/SQLTemplateDescriptorTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/map/SQLTemplateDescriptorTest.java
@@ -25,6 +25,7 @@ import org.apache.cayenne.query.*;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -54,16 +55,37 @@ public class SQLTemplateDescriptorTest {
     @Test
     public void getQueryProperties() throws Exception {
         SQLTemplateDescriptor builder = QueryDescriptor.sqlTemplateDescriptor();
-        builder.setProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, "5");
-        builder.setProperty(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY, "6");
-
-        Query query = builder.buildQuery();
-        assertTrue(query instanceof SQLTemplate);
-        assertEquals(5, ((SQLTemplate) query).getFetchLimit());
-        
-        assertEquals(6, ((SQLTemplate) query).getStatementFetchSize());
+        builder.setProperty(QueryDescriptor.FETCH_LIMIT_PROPERTY, "5");
+        builder.setProperty(QueryDescriptor.FETCH_OFFSET_PROPERTY, "2");
+        builder.setProperty(QueryDescriptor.PAGE_SIZE_PROPERTY, "10");
+        builder.setProperty(QueryDescriptor.STATEMENT_FETCH_SIZE_PROPERTY, "6");
+        builder.setProperty(QueryDescriptor.FETCHING_DATA_ROWS_PROPERTY, "true");
+        builder.setProperty(QueryDescriptor.CACHE_STRATEGY_PROPERTY, "LOCAL_CACHE");
+        builder.setProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY, "g1");
+        builder.setProperty(SQLTemplateDescriptor.COLUMN_NAME_CAPITALIZATION_PROPERTY, "lower");
+
+        SQLTemplate<?> query = builder.buildQuery();
+        assertEquals(5, query.getFetchLimit());
+        assertEquals(2, query.getFetchOffset());
+        assertEquals(10, query.getPageSize());
+        assertEquals(6, query.getStatementFetchSize());
+        assertTrue(query.isFetchingDataRows());
+        assertEquals(QueryCacheStrategy.LOCAL_CACHE, query.getCacheStrategy());
+        assertEquals("g1", query.getCacheGroup());
+        assertEquals(CapsStrategy.LOWER, query.getColumnNamesCapitalization());
+    }
 
-        // TODO: test other properties...
+    @Test
+    public void getQueryPropertiesDefaults() throws Exception {
+        SQLTemplate<?> query = QueryDescriptor.sqlTemplateDescriptor().buildQuery();
+        assertEquals(0, query.getFetchLimit());
+        assertEquals(0, query.getFetchOffset());
+        assertEquals(0, query.getPageSize());
+        assertEquals(0, query.getStatementFetchSize());
+        assertFalse(query.isFetchingDataRows());
+        assertEquals(QueryCacheStrategy.NO_CACHE, query.getCacheStrategy());
+        assertNull(query.getCacheGroup());
+        assertEquals(CapsStrategy.DEFAULT, query.getColumnNamesCapitalization());
     }
 
     @Test
diff --git a/cayenne/src/test/java/org/apache/cayenne/map/SelectQueryDescriptorTest.java b/cayenne/src/test/java/org/apache/cayenne/map/SelectQueryDescriptorTest.java
index 69815aac9..1201f34c9 100644
--- a/cayenne/src/test/java/org/apache/cayenne/map/SelectQueryDescriptorTest.java
+++ b/cayenne/src/test/java/org/apache/cayenne/map/SelectQueryDescriptorTest.java
@@ -23,7 +23,7 @@ import java.util.Map;
 
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.query.ObjectSelect;
-import org.apache.cayenne.query.QueryMetadata;
+import org.apache.cayenne.query.QueryCacheStrategy;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.*;
@@ -85,14 +85,55 @@ public class SelectQueryDescriptorTest {
     public void getQueryProperties() {
         SelectQueryDescriptor builder = QueryDescriptor.selectQueryDescriptor();
         builder.setRoot("FakeRoot");
-        builder.setProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, "5");
-        builder.setProperty(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY, "6");
+        builder.setProperty(QueryDescriptor.FETCH_LIMIT_PROPERTY, "5");
+        builder.setProperty(QueryDescriptor.FETCH_OFFSET_PROPERTY, "2");
+        builder.setProperty(QueryDescriptor.PAGE_SIZE_PROPERTY, "10");
+        builder.setProperty(QueryDescriptor.STATEMENT_FETCH_SIZE_PROPERTY, "6");
+        builder.setProperty(QueryDescriptor.FETCHING_DATA_ROWS_PROPERTY, "true");
+        builder.setProperty(QueryDescriptor.CACHE_STRATEGY_PROPERTY, "SHARED_CACHE");
+        builder.setProperty(QueryDescriptor.CACHE_GROUPS_PROPERTY, "g1");
+        builder.setProperty(SelectQueryDescriptor.DISTINCT_PROPERTY, "true");
 
         ObjectSelect<?> query = builder.buildQuery();
-        assertTrue(query instanceof ObjectSelect);
         assertEquals(5, query.getLimit());
+        assertEquals(2, query.getOffset());
+        assertEquals(10, query.getPageSize());
         assertEquals(6, query.getStatementFetchSize());
+        assertTrue(query.isFetchingDataRows());
+        assertEquals(QueryCacheStrategy.SHARED_CACHE, query.getCacheStrategy());
+        assertEquals("g1", query.getCacheGroup());
+        assertTrue(query.isDistinct());
+    }
+
+    @Test
+    public void getQueryPropertiesDefaults() {
+        SelectQueryDescriptor builder = QueryDescriptor.selectQueryDescriptor();
+        builder.setRoot("FakeRoot");
+
+        ObjectSelect<?> query = builder.buildQuery();
+        assertEquals(0, query.getLimit());
+        assertEquals(0, query.getOffset());
+        assertEquals(0, query.getPageSize());
+        assertEquals(0, query.getStatementFetchSize());
+        assertFalse(query.isFetchingDataRows());
+        assertEquals(QueryCacheStrategy.NO_CACHE, query.getCacheStrategy());
+        assertNull(query.getCacheGroup());
+        assertFalse(query.isDistinct());
+    }
 
-        // TODO: test other properties...
+    @Test
+    public void typedSetters() {
+        SelectQueryDescriptor builder = QueryDescriptor.selectQueryDescriptor();
+        builder.setRoot("FakeRoot");
+        builder.setFetchLimit(5);
+        builder.setCacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
+        builder.setCacheGroup("g1");
+        builder.setFetchingDataRows(true);
+
+        ObjectSelect<?> query = builder.buildQuery();
+        assertEquals(5, query.getLimit());
+        assertEquals(QueryCacheStrategy.LOCAL_CACHE, query.getCacheStrategy());
+        assertEquals("g1", query.getCacheGroup());
+        assertTrue(query.isFetchingDataRows());
     }
 }