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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 5b8757c47e06eb6e51c0c1f5316f4f64a703b472
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Jul 23 16:05:15 2025 +0200

    Partial update of the EPSG geodetic dataset from version 9.9.1 to version 
12.013.
    The "source_geogcrs_code" column is renamed "base_crs_code", but a 
compatibility
    mode is kept if the previous name is detected.
    
    With this commit, the "ensemble" datum type in the "Datum" table is 
recognized.
    Starting from this commit, Apache SIS can read at least the CRSs from EPSG 
10+,
    but not all metadata are read and the WKT 2 format is not yet updated.
---
 .../apache/sis/io/wkt/GeodeticObjectParser.java    |   2 +-
 .../sis/referencing/crs/DefaultGeodeticCRS.java    |  24 +-
 .../referencing/datum/DefaultDatumEnsemble.java    | 122 +++++----
 .../factory/GeodeticAuthorityFactory.java          |  68 ++++-
 .../sis/referencing/factory/sql/AxisName.java      |  18 +-
 .../factory/sql/CoordinateOperationSet.java        |   2 +-
 .../referencing/factory/sql/EPSGCodeFinder.java    |   4 +-
 .../referencing/factory/sql/EPSGDataAccess.java    | 276 +++++++++++++--------
 .../sis/referencing/factory/sql/EPSG_Finish.sql    |   2 +-
 .../sis/referencing/factory/sql/SQLTranslator.java | 159 +++++++-----
 .../internal/PositionalAccuracyConstant.java       |  40 ++-
 .../org/apache/sis/util/resources/Vocabulary.java  |   5 +
 .../sis/util/resources/Vocabulary.properties       |   1 +
 .../sis/util/resources/Vocabulary_fr.properties    |   1 +
 .../referencing/factory/sql/epsg/DebugTools.sql    |   2 +-
 15 files changed, 487 insertions(+), 239 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
index 7751ec3922..2b969c5339 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/io/wkt/GeodeticObjectParser.java
@@ -2315,7 +2315,7 @@ class GeodeticObjectParser extends MathTransformParser 
implements Comparator<Coo
         final Map<String,Object>        properties       = 
parseParametersAndClose(element, name, method);
         if (accuracy != null) {
             
properties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
-                    
PositionalAccuracyConstant.create(accuracy.pullDouble("accuracy")));
+                    
PositionalAccuracyConstant.transformation(accuracy.pullDouble("accuracy")));
             accuracy.close(ignoredElements);
         }
         try {
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
index 7fa342873c..e1ad9ffe9b 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/crs/DefaultGeodeticCRS.java
@@ -33,6 +33,7 @@ import org.opengis.referencing.datum.PrimeMeridian;
 import org.apache.sis.referencing.AbstractReferenceSystem;
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.cs.AbstractCS;
+import org.apache.sis.referencing.datum.DatumOrEnsemble;
 import org.apache.sis.referencing.internal.Legacy;
 import org.apache.sis.referencing.privy.AxisDirections;
 import org.apache.sis.referencing.privy.WKTKeywords;
@@ -214,18 +215,19 @@ class DefaultGeodeticCRS extends 
AbstractSingleCRS<GeodeticDatum> implements Geo
         formatter.newLine();
         formatter.append(WKTUtilities.toFormattable(datum));
         formatter.newLine();
-        final PrimeMeridian pm = datum.getPrimeMeridian();
         final Unit<Angle> angularUnit = AxisDirections.getAngularUnit(cs, 
null);
-        if (convention != Convention.WKT2_SIMPLIFIED ||     // Really this 
specific enum, not Convention.isSimplified().
-                ReferencingUtilities.getGreenwichLongitude(pm, Units.DEGREE) 
!= 0)
-        {
-            final Unit<Angle> oldUnit = 
formatter.addContextualUnit(angularUnit);
-            formatter.indent(1);
-            formatter.append(WKTUtilities.toFormattable(pm));
-            formatter.indent(-1);
-            formatter.newLine();
-            formatter.restoreContextualUnit(angularUnit, oldUnit);
-        }
+        DatumOrEnsemble.getPrimeMeridian(this).ifPresent((PrimeMeridian pm) -> 
{
+            if (convention != Convention.WKT2_SIMPLIFIED ||     // Really this 
specific enum, not Convention.isSimplified().
+                    ReferencingUtilities.getGreenwichLongitude(pm, 
Units.DEGREE) != 0)
+            {
+                final Unit<Angle> oldUnit = 
formatter.addContextualUnit(angularUnit);
+                formatter.indent(1);
+                formatter.append(WKTUtilities.toFormattable(pm));
+                formatter.indent(-1);
+                formatter.newLine();
+                formatter.restoreContextualUnit(angularUnit, oldUnit);
+            }
+        });
         /*
          * Get the coordinate system to format. This will also determine the 
units to write and the keyword to
          * return in WKT 1 format. Note that for the WKT 1 format, we need to 
replace the coordinate system by
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
index 3345849257..26b7d5bb3d 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/datum/DefaultDatumEnsemble.java
@@ -45,6 +45,7 @@ import org.apache.sis.referencing.internal.Resources;
 import org.apache.sis.referencing.privy.WKTKeywords;
 import org.apache.sis.metadata.privy.SecondaryTrait;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.Classes;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.Utilities;
 import org.apache.sis.util.resources.Errors;
@@ -144,8 +145,8 @@ public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObj
      * @param  properties  the properties to be given to the identified object.
      * @param  members     datum or reference frames which are members of this 
ensemble.
      * @param  accuracy    inaccuracy introduced through use of this ensemble 
(mandatory).
-     * @param  type        the base type of datum members contained in this 
ensemble.
-     * @throws ClassCastException if a member is not an instance of {@code 
type}.
+     * @param  memberType  the base type of datum members contained in this 
ensemble.
+     * @throws ClassCastException if a member is not an instance of {@code 
memberType}.
      * @throws IllegalArgumentException if a member is an instance of {@link 
DatumEnsemble}, of if at least two
      *         different {@linkplain AbstractDatum#getConventionalRS() 
conventional reference systems} are found.
      *
@@ -154,13 +155,13 @@ public class DefaultDatumEnsemble<D extends Datum> 
extends AbstractIdentifiedObj
     protected DefaultDatumEnsemble(final Map<String,?> properties,
                                    final Collection<? extends D> members,
                                    final PositionalAccuracy accuracy,
-                                   final Class<? super D> type)
+                                   final Class<D> memberType)
     {
         super(properties);
         ArgumentChecks.ensureNonNull("accuracy", accuracy);
         ensembleAccuracy = accuracy;
         this.members = List.copyOf(members);
-        validate(type);
+        validate(memberType);
     }
 
     /**
@@ -170,40 +171,40 @@ public class DefaultDatumEnsemble<D extends Datum> 
extends AbstractIdentifiedObj
      *
      * <p>This constructor performs a shallow copy, i.e. the properties are 
not cloned.</p>
      *
-     * @param  ensemble  the ensemble to copy.
-     * @param  type      the base type of datum members contained in this 
ensemble.
-     * @throws ClassCastException if a member is not an instance of {@code 
type}.
+     * @param  ensemble    the ensemble to copy.
+     * @param  memberType  the base type of datum members contained in this 
ensemble.
+     * @throws ClassCastException if a member is not an instance of {@code 
memberType}.
      * @throws IllegalArgumentException if a member is an instance of {@link 
DatumEnsemble}, of if at least two
      *         different {@linkplain AbstractDatum#getConventionalRS() 
conventional reference systems} are found.
      *
      * @see #castOrCopy(DatumEnsemble)
      */
-    protected DefaultDatumEnsemble(final DatumEnsemble<? extends D> ensemble, 
final Class<? super D> type) {
+    protected DefaultDatumEnsemble(final DatumEnsemble<? extends D> ensemble, 
final Class<D> memberType) {
         super(ensemble);
         members = List.copyOf(ensemble.getMembers());
         ensembleAccuracy = 
Objects.requireNonNull(ensemble.getEnsembleAccuracy());
-        validate(type);
+        validate(memberType);
     }
 
     /**
      * Verifies this ensemble. All members shall be instances of the specified 
type and shall have
      * the same conventional reference system. No member can be an instance of 
{@link DatumEnsemble}.
      *
-     * @param  type  the base type of datum members contained in this ensemble.
-     * @throws ClassCastException if a member is not an instance of {@code 
type}.
+     * @param  memberType  the base type of datum members contained in this 
ensemble.
+     * @throws ClassCastException if a member is not an instance of {@code 
memberType}.
      * @throws IllegalArgumentException if a member is an instance of {@link 
DatumEnsemble}, of if at least two
      *         different {@linkplain AbstractDatum#getConventionalRS() 
conventional reference systems} are found.
      */
-    private void validate(final Class<? super D> type) {
+    private void validate(final Class<D> memberType) {
         IdentifiedObject rs = null;
         for (final D datum : members) {
             if (datum instanceof DatumEnsemble<?>) {
                 throw new IllegalArgumentException(
                         Errors.format(Errors.Keys.IllegalPropertyValueClass_2, 
"members", DatumEnsemble.class));
             }
-            if (!type.isInstance(datum)) {
+            if (!memberType.isInstance(datum)) {
                 throw new ClassCastException(
-                        Errors.format(Errors.Keys.IllegalClass_2, type, 
datum.getClass()));
+                        Errors.format(Errors.Keys.IllegalClass_2, memberType, 
Classes.getClass(datum)));
             }
             final IdentifiedObject dr = datum.getConventionalRS().orElse(null);
             if (dr != null) {
@@ -235,7 +236,7 @@ public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObj
             final Collection<? extends D> members,
             final PositionalAccuracy accuracy)
     {
-        return Factory.forMemberType(null, properties, List.copyOf(members), 
accuracy);
+        return Factory.forMemberType(Datum.class, null, properties, 
List.copyOf(members), accuracy);
     }
 
     /**
@@ -264,7 +265,36 @@ public class DefaultDatumEnsemble<D extends Datum> extends 
AbstractIdentifiedObj
         if (object == null || object instanceof DefaultDatumEnsemble<?>) {
             return (DefaultDatumEnsemble<D>) object;
         }
-        return Factory.forMemberType(object, null, 
List.copyOf(object.getMembers()), null);
+        return Factory.forMemberType(Datum.class, object, null, 
List.copyOf(object.getMembers()), object.getEnsembleAccuracy());
+    }
+
+    /**
+     * Returns this datum ensemble as a collection of datum of the given type.
+     * This method casts the datum members, it does not copy or rewrite them.
+     * However, the returned {@code DatumEnsemble} may be a different instance.
+     *
+     * @param  <N>         compile-time value of {@code memberType}.
+     * @param  memberType  the new desired type of datum members.
+     * @return an ensemble of datum of the given type.
+     * @throws ClassCastException if at least one member is not an instance of 
the specified type.
+     */
+    public <N extends Datum> DefaultDatumEnsemble<N> cast(final Class<N> 
memberType) {
+        for (final D member : members) {
+            if (!memberType.isInstance(member)) {
+                throw new 
ClassCastException(Errors.format(Errors.Keys.IllegalClass_2, memberType, 
Classes.getClass(member)));
+            }
+        }
+        /*
+         * At this point, we verified that all members are of the requested 
type.
+         * Now verify if the ensemble as a whole is also of the requested type.
+         * This part is not mandatory however.
+         */
+        @SuppressWarnings("unchecked")
+        DefaultDatumEnsemble<N> ensemble = (DefaultDatumEnsemble<N>) this;
+        if (!memberType.isInstance(ensemble)) {
+            ensemble = Factory.forMemberType(memberType, ensemble, null, 
ensemble.members, ensemble.ensembleAccuracy);
+        }
+        return ensemble;
     }
 
     /**
@@ -297,12 +327,13 @@ public class DefaultDatumEnsemble<D extends Datum> 
extends AbstractIdentifiedObj
 
     /**
      * Returns the datum or reference frames which are members of this 
ensemble.
+     * The returned list is immutable.
      *
      * @return datum or reference frames which are members of this ensemble.
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")     // Collection is 
unmodifiable.
-    public Collection<D> getMembers() {
+    public final Collection<D> getMembers() {               // Must be final 
for type safety. See `forMemberType(…)`
         return members;
     }
 
@@ -685,15 +716,15 @@ check:  if (it.hasNext()) {
         /**
          * Base type of all members in the ensembles constructed by this 
factory instance.
          */
-        private final Class<D> type;
+        private final Class<D> memberType;
 
         /**
          * Creates a new factory for ensembles in which all members are 
instances of the given type.
          *
-         * @param  type  base type of all members in the ensembles constructed 
by this factory instance.
+         * @param  memberType  base type of all members in the ensembles 
constructed by this factory instance.
          */
-        private Factory(final Class<D> type) {
-            this.type = type;
+        private Factory(final Class<D> memberType) {
+            this.memberType = memberType;
         }
 
         /**
@@ -703,12 +734,14 @@ check:  if (it.hasNext()) {
          *
          * <ul>
          *   <li>{@link #getMembers()}, which is a read-only collection (it is 
safe to cast {@code List<? extends D>}
-         *       as {@code List<D>} when no write operation is allowed).</li>
+         *       as {@code List<D>} when no write operation is allowed). That 
method is made final for enforcing this
+         *       assumption.</li>
          * </ul>
          *
          * Exactly one of {@code object} and {@code properties} should be 
non-null.
          *
          * @param  <D>          base type of all members in the ensembles to 
create.
+         * @param  memberType   the base class of datum type to take in 
account.
          * @param  object       the source ensemble to copy, or {@code null} 
if none.
          * @param  properties   the properties of the ensemble to create, or 
{@code null}.
          * @param  members      members of the ensemble to copy or create.
@@ -718,35 +751,40 @@ check:  if (it.hasNext()) {
          *         Should never happen if the parameterized type of {@code 
members} is respected.
          */
         static <D extends Datum> DefaultDatumEnsemble<D> forMemberType(
+                final Class<? extends Datum> memberType,
                 final DatumEnsemble<? extends D> object,
                 final Map<String,?> properties,
                 final List<? extends D> members,
                 final PositionalAccuracy accuracy)
         {
+            Object illegal = null;
 nextType:   for (final Factory<?> factory : FACTORIES) {
-                for (final Object member : members) {
-                    if (!factory.type.isInstance(member)) {
-                        continue nextType;
+                if (memberType.isAssignableFrom(factory.memberType)) {
+                    for (final Object member : members) {
+                        if (!factory.memberType.isInstance(member)) {
+                            illegal = member;
+                            continue nextType;
+                        }
+                    }
+                    /*
+                     * A more correct type would be `Factory<? super D>` 
because of the use of `isInstance(member)`
+                     * instead of strict class equality. However, even `? 
super D` is not guaranteed to be correct,
+                     * because nothing prevent a member from implementing two 
interfaces. In such case, the type of
+                     * the first matching factory could be unrelated to `D` 
(e.g., `D` is `ParametricDatum` but all
+                     * members also implement `VerticalDatum`). However, 
despite this uncertainty, the cast is okay
+                     * because `D` appears only in `getMembers()` which 
returns a read-only collection. There is no
+                     * method returning `Class<D>` and no guarantees that the 
returned object will implement `D`.
+                     */
+                    @SuppressWarnings("unchecked")
+                    final var selected = (Factory<D>) factory;      // See 
above comment.
+                    if (object != null) {
+                        return selected.copy(object);
+                    } else {
+                        return selected.create(properties, members, accuracy);
                     }
-                }
-                /*
-                 * A more correct type would be `Factory<? super D>` because 
of the use of `isInstance(member)`
-                 * instead of strict class equality. However, even `? super D` 
is not guaranteed to be correct,
-                 * because nothing prevent a member from implementing two 
interfaces. In such case, the type of
-                 * the first matching factory could be unrelated to `D` (e.g., 
`D` is `ParametricDatum` but all
-                 * members also implement `VerticalDatum`). However, despite 
this uncertainty, the cast is okay
-                 * because `D` appears only in `getMembers()` which returns a 
read-only collection. There is no
-                 * method returning `Class<D>` and no guarantees that the 
returned object will implement `D`.
-                 */
-                @SuppressWarnings("unchecked")
-                final var selected = (Factory<D>) factory;      // See above 
comment.
-                if (object != null) {
-                    return selected.copy(object);
-                } else {
-                    return selected.create(properties, members, accuracy);
                 }
             }
-            throw new ClassCastException();
+            throw new 
ClassCastException(Errors.format(Errors.Keys.IllegalClass_2, Datum.class, 
Classes.getClass(illegal)));
         }
 
         /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
index 0c5056725c..8016fd352c 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/GeodeticAuthorityFactory.java
@@ -537,13 +537,18 @@ public abstract class GeodeticAuthorityFactory extends 
AbstractFactory implement
      * <table class="sis">
      * <caption>Authority codes examples</caption>
      *   <tr><th>Code</th>      <th>Type</th>        <th>Description</th></tr>
-     *   <tr><td>EPSG:6326</td> <td>Geodetic</td>    <td>World Geodetic System 
1984</td></tr>
+     *   <tr><td>EPSG:6326</td> <td>Ensemble</td>    <td>World Geodetic System 
1984</td></tr>
      *   <tr><td>EPSG:6322</td> <td>Geodetic</td>    <td>World Geodetic System 
1972</td></tr>
      *   <tr><td>EPSG:1027</td> <td>Vertical</td>    <td>EGM2008 
geoid</td></tr>
      *   <tr><td>EPSG:5100</td> <td>Vertical</td>    <td>Mean Sea 
Level</td></tr>
      *   <tr><td>EPSG:9315</td> <td>Engineering</td> <td>Seismic bin grid 
datum</td></tr>
      * </table>
      *
+     * <p><b>Note:</b> strictly speaking, the reference frames of type 
<i>ensemble</i> cannot be returned by this method.
+     * They should be obtained by the {@link #createDatumEnsemble(String)} 
method instead. In the particular case of the
+     * Apache <abbr>SIS</abbr> implementation, datum ensembles can 
nevertheless be obtained by this method, but this is
+     * not guaranteed to be true for all implementations of the {@link 
DatumAuthorityFactory} interface.</p>
+     *
      * <h4>Default implementation</h4>
      * The default implementation delegates to {@link #createObject(String)} 
and casts the result.
      * If the result cannot be cast, then a {@link 
NoSuchAuthorityCodeException} is thrown.
@@ -571,13 +576,18 @@ public abstract class GeodeticAuthorityFactory extends 
AbstractFactory implement
      *
      * <table class="sis">
      * <caption>Authority codes examples</caption>
-     *   <tr><th>Code</th>      <th>Description</th></tr>
-     *   <tr><td>EPSG:6326</td> <td>World Geodetic System 1984</td></tr>
-     *   <tr><td>EPSG:6322</td> <td>World Geodetic System 1972</td></tr>
-     *   <tr><td>EPSG:6269</td> <td>North American Datum 1983</td></tr>
-     *   <tr><td>EPSG:6258</td> <td>European Terrestrial Reference System 
1989</td></tr>
+     *   <tr><th>Code</th>      <th>Type</th>     <th>Description</th></tr>
+     *   <tr><td>EPSG:6326</td> <td>Ensemble</td> <td>World Geodetic System 
1984</td></tr>
+     *   <tr><td>EPSG:6322</td> <td>Dynamic</td>  <td>World Geodetic System 
1972</td></tr>
+     *   <tr><td>EPSG:6269</td> <td></td>         <td>North American Datum 
1983</td></tr>
+     *   <tr><td>EPSG:6258</td> <td>Ensemble</td> <td>European Terrestrial 
Reference System 1989</td></tr>
      * </table>
      *
+     * <p><b>Note:</b> strictly speaking, the reference frames of type 
<i>ensemble</i> cannot be returned by this method.
+     * They should be obtained by the {@link #createDatumEnsemble(String)} 
method instead. In the particular case of the
+     * Apache <abbr>SIS</abbr> implementation, datum ensembles can 
nevertheless be obtained by this method, but this is
+     * not guaranteed to be true for all implementations of the {@link 
DatumAuthorityFactory} interface.</p>
+     *
      * <h4>Default implementation</h4>
      * The default implementation delegates to {@link #createDatum(String)} 
and casts the result.
      * If the result cannot be cast, then a {@link 
NoSuchAuthorityCodeException} is thrown.
@@ -698,6 +708,45 @@ public abstract class GeodeticAuthorityFactory extends 
AbstractFactory implement
         return cast(EngineeringDatum.class, createDatum(code), code);
     }
 
+    /**
+     * Creates an arbitrary datum ensemble from a code.
+     * A datum ensemble is a collection of datums which for low accuracy 
requirements
+     * may be considered to be insignificantly different from each other.
+     *
+     * <h4>Examples</h4>
+     * The {@linkplain #getAuthorityCodes(Class) set of available codes} 
depends on the defining
+     * {@linkplain #getAuthority() authority} and the {@code 
GeodeticAuthorityFactory} subclass in use.
+     * A frequently used authority is "EPSG", which includes the following 
codes:
+     *
+     * <table class="sis">
+     * <caption>Authority codes examples</caption>
+     *   <tr><th>Code</th>      <th>Description</th></tr>
+     *   <tr><td>EPSG:6326</td> <td>World Geodetic System 1984</td></tr>
+     *   <tr><td>EPSG:6258</td> <td>European Terrestrial Reference System 
1989</td></tr>
+     * </table>
+     *
+     * <h4>Default implementation</h4>
+     * The default implementation delegates to {@link #createDatum(String)} 
and casts the result.
+     * If the result cannot be cast, then a {@link 
NoSuchAuthorityCodeException} is thrown.
+     * Note that the delegation to {@code createDatum(…)} works only if the 
implementation
+     * stores datum and datum ensembles together. This is the case of the 
<abbr>EPSG</abbr>
+     * geodetic dataset for instance. In such case, the datum ensemble can be 
returned as an
+     * object that implements both the {@link Datum} and {@link DatumEnsemble} 
interfaces,
+     * such as the {@link 
org.apache.sis.referencing.datum.DefaultDatumEnsemble} class.
+     *
+     * @param  code  value allocated by authority.
+     * @return the datum for the given code.
+     * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
+     * @throws FactoryException if the object creation failed for some other 
reason.
+     *
+     * @see org.apache.sis.referencing.datum.DefaultDatumEnsemble
+     *
+     * @since 1.5
+     */
+    public DatumEnsemble<?> createDatumEnsemble(final String code) throws 
NoSuchAuthorityCodeException, FactoryException {
+        return cast(DatumEnsemble.class, createDatum(code), code);
+    }
+
     /**
      * Creates a geometric figure that can be used to describe the approximate 
shape of the earth.
      * In mathematical terms, it is a surface formed by the rotation of an 
ellipse about its minor axis.
@@ -1315,8 +1364,11 @@ public abstract class GeodeticAuthorityFactory extends 
AbstractFactory implement
          */
         final Identifier id = object.getName();
         final Citation authority = (id != null) ? id.getAuthority() : 
getAuthority();
-        throw new 
NoSuchAuthorityCodeException(Errors.format(Errors.Keys.UnexpectedTypeForReference_3,
 code, type, actual),
-                Citations.getIdentifier(authority), trimNamespace(code), code);
+        throw new NoSuchAuthorityCodeException(
+                Errors.format(Errors.Keys.UnexpectedTypeForReference_3, code, 
type, actual),
+                Citations.getIdentifier(authority),
+                trimNamespace(code),
+                code);
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AxisName.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AxisName.java
index 5713f8ead8..5009c16488 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AxisName.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/AxisName.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.factory.sql;
 
 import java.util.Objects;
+import org.apache.sis.pending.jdk.Record;
 
 
 /**
@@ -24,7 +25,7 @@ import java.util.Objects;
  *
  * @author  Martin Desruisseaux (IRD)
  */
-final class AxisName {
+final class AxisName extends Record {
     /**
      * The coordinate system axis name (never {@code null}).
      */
@@ -32,15 +33,22 @@ final class AxisName {
 
     /**
      * The coordinate system axis description, or {@code null} if none.
+     * Example: "Angle from the equatorial plane to the perpendicular to the 
ellipsoid".
      */
     final String description;
 
+    /**
+     * The remarks. Example: "Used in geographic 2D and geographic 3D 
coordinate reference systems".
+     */
+    final String remarks;
+
     /**
      * Creates a new coordinate system axis name.
      */
-    AxisName(final String name, final String description) {
+    AxisName(final String name, final String description, final String 
remarks) {
         this.name = name;
         this.description = description;
+        this.remarks = remarks;
     }
 
     /**
@@ -57,8 +65,10 @@ final class AxisName {
     @Override
     public boolean equals(final Object object) {
         if (object instanceof AxisName) {
-            final AxisName that = (AxisName) object;
-            return name.equals(that.name) && Objects.equals(description, 
that.description);
+            final var that = (AxisName) object;
+            return name.equals(that.name) &&
+                    Objects.equals(description, that.description) &&
+                    Objects.equals(remarks, that.remarks);
         }
         return false;
     }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
index c52f883131..6002437391 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/CoordinateOperationSet.java
@@ -44,7 +44,7 @@ import org.opengis.referencing.crs.DerivedCRS;
  *       one result, because {@code COORD_REF_SYS_CODE} is a primary key):
  *
  *       {@snippet lang="sql" :
- *         SELECT PROJECTION_CONV_CODE FROM "Coordinate Reference System" 
WHERE SOURCE_GEOGCRS_CODE = ? AND COORD_REF_SYS_CODE = ?
+ *         SELECT PROJECTION_CONV_CODE FROM "Coordinate Reference System" 
WHERE BASE_CRS_CODE = ? AND COORD_REF_SYS_CODE = ?
  *         }
  *   </li>
  *
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
index a925a30e9b..d2f4d66493 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGCodeFinder.java
@@ -310,7 +310,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
             }
             /*
              * For Coordinate Reference System, the SQL statement may be 
something like below
-             * (with DATUM_CODE replaced by SOURCE_GEOGCRS_CODE in a projected 
CRS):
+             * (with DATUM_CODE replaced by BASE_CRS_CODE projected CRS):
              *
              *   SELECT COORD_REF_SYS_CODE FROM "Coordinate Reference System"
              *     WHERE CAST(COORD_REF_SYS_KIND AS VARCHAR(80)) LIKE 
'geographic%'
@@ -319,7 +319,7 @@ crs:    if (isInstance(CoordinateReferenceSystem.class, 
object)) {
              */
             final Condition filter;
             if (object instanceof DerivedCRS) {              // No need to use 
isInstance(Class, Object) from here.
-                filter = dependencies("SOURCE_GEOGCRS_CODE", SingleCRS.class, 
((DerivedCRS) object).getBaseCRS(), true);
+                filter = dependencies("BASE_CRS_CODE", SingleCRS.class, 
((DerivedCRS) object).getBaseCRS(), true);
             } else if (object instanceof GeodeticCRS) {
                 filter = dependencies("DATUM_CODE", GeodeticDatum.class, 
((GeodeticCRS) object).getDatum(), true);
             } else if (object instanceof VerticalCRS) {
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index 3f3f06c54c..0b3b995688 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -71,6 +71,7 @@ import org.apache.sis.referencing.ImmutableIdentifier;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 import org.apache.sis.referencing.cs.CoordinateSystems;
 import org.apache.sis.referencing.datum.BursaWolfParameters;
+import org.apache.sis.referencing.datum.DefaultDatumEnsemble;
 import org.apache.sis.referencing.datum.DefaultGeodeticDatum;
 import org.apache.sis.referencing.operation.DefaultOperationMethod;
 import org.apache.sis.referencing.operation.DefaultCoordinateOperationFactory;
@@ -88,7 +89,7 @@ import 
org.apache.sis.referencing.internal.ParameterizedTransformBuilder;
 import org.apache.sis.referencing.internal.PositionalAccuracyConstant;
 import org.apache.sis.referencing.internal.SignReversalComment;
 import org.apache.sis.referencing.internal.Resources;
-import static 
org.apache.sis.referencing.internal.ServicesForMetadata.CONNECTION;
+import org.apache.sis.referencing.internal.ServicesForMetadata;
 import org.apache.sis.parameter.DefaultParameterDescriptor;
 import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
 import org.apache.sis.system.Loggers;
@@ -98,6 +99,7 @@ import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.Version;
+import org.apache.sis.util.Utilities;
 import org.apache.sis.util.Workaround;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.collection.BackingStoreException;
@@ -108,8 +110,6 @@ import org.apache.sis.util.privy.Constants;
 import org.apache.sis.util.privy.CollectionsExt;
 import org.apache.sis.util.privy.Strings;
 import org.apache.sis.util.privy.URLs;
-import static org.apache.sis.util.privy.Constants.UTC;
-import static org.apache.sis.util.Utilities.equalsIgnoreMetadata;
 import org.apache.sis.temporal.LenientDateFormat;
 import org.apache.sis.metadata.iso.citation.DefaultCitation;
 import org.apache.sis.metadata.iso.citation.DefaultOnlineResource;
@@ -261,7 +261,7 @@ public class EPSGDataAccess extends 
GeodeticAuthorityFactory implements CRSAutho
 
     /**
      * Cache for axis names. This service is not provided by {@code 
ConcurrentAuthorityFactory}
-     * since {@link AxisName} objects are particular to the EPSG database.
+     * because {@link AxisName} objects are specific to the <abbr>EPSG</abbr> 
authority factory.
      *
      * @see #getAxisName(Integer)
      */
@@ -271,7 +271,7 @@ public class EPSGDataAccess extends 
GeodeticAuthorityFactory implements CRSAutho
      * Cache of naming systems other than EPSG. There is usually few of them 
(at most 15).
      * This is used for aliases.
      *
-     * @see #createProperties(String, String, Integer, CharSequence, boolean)
+     * @see #createProperties(String, Integer, String, CharSequence, String, 
String, CharSequence, boolean)
      */
     private final Map<String,NameSpace> namingSystems = new HashMap<>();
 
@@ -381,7 +381,7 @@ public class EPSGDataAccess extends 
GeodeticAuthorityFactory implements CRSAutho
     @SuppressWarnings("ReturnOfDateField")
     private Calendar getCalendar() {
         if (calendar == null) {
-            calendar = Calendar.getInstance(TimeZone.getTimeZone(UTC), 
Locale.CANADA);
+            calendar = 
Calendar.getInstance(TimeZone.getTimeZone(Constants.UTC), Locale.CANADA);
             // Canada locale is closer to ISO than US.
         }
         calendar.clear();
@@ -455,7 +455,7 @@ addURIs:    for (int i=0; ; i++) {
                     case 1: url = URLs.EPSG; function = 
OnLineFunction.DOWNLOAD; break;
                     case 2: {
                         url = SQLUtilities.getSimplifiedURL(metadata);
-                        function = OnLineFunction.valueOf(CONNECTION);
+                        function = 
OnLineFunction.valueOf(ServicesForMetadata.CONNECTION);
                         description = 
Resources.formatInternational(Resources.Keys.GeodeticDataBase_4,
                                 Constants.EPSG, version, 
metadata.getDatabaseProductName(),
                                 
Version.valueOf(metadata.getDatabaseMajorVersion(),
@@ -639,7 +639,7 @@ addURIs:    for (int i=0; ; i++) {
     }
 
     /**
-     * Returns {@code true} if the specified code may be a primary key in some 
table.
+     * Returns {@code true} if the specified code may be a primary key in some 
tables.
      * This method does not need to check any entry in the database.
      * It should just check from the syntax if the code looks like a valid 
EPSG identifier.
      *
@@ -654,12 +654,14 @@ addURIs:    for (int i=0; ; i++) {
      *
      * <h4>Default implementation</h4>
      * The default implementation returns {@code true} if all characters are 
decimal digits 0 to 9.
+     * Currently, this default implementation cannot be overridden. But we may 
allow that in a future
+     * version if it appears to be useful.
      *
      * @param  code  the code the inspect.
      * @return {@code true} if the code is probably a primary key.
      * @throws FactoryException if an unexpected error occurred while 
inspecting the code.
      */
-    private boolean isPrimaryKey(final String code) throws FactoryException {
+    private static boolean isPrimaryKey(final String code) throws 
FactoryException {
         int i = code.length();
         if (i == 0) {
             return false;
@@ -901,11 +903,12 @@ codes:  for (int i=0; i<codes.length; i++) {
     /**
      * Formats an error message for an unexpected null value.
      */
+    @SuppressWarnings("ConvertToTryWithResources")
     private String nullValue(final ResultSet result, final int columnIndex, 
final Comparable<?> code) throws SQLException {
         final ResultSetMetaData metadata = result.getMetaData();
         final String column = metadata.getColumnName(columnIndex);
         final String table  = metadata.getTableName (columnIndex);
-        result.close();
+        result.close();     // Only an optimization. The actual 
try-with-resource is done by the caller.
         return error().getString(Errors.Keys.NullValueInTable_3, table, 
column, code);
     }
 
@@ -1094,15 +1097,25 @@ codes:  for (int i=0; i<codes.length; i++) {
      * Returns the name and aliases for the {@link IdentifiedObject} to 
construct.
      *
      * @param  table       the table on which a query has been executed.
-     * @param  name        the name for the {@link IdentifiedObject} to 
construct.
      * @param  code        the EPSG code of the object to construct.
+     * @param  name        the name for the {@link IdentifiedObject} to 
construct.
+     * @param  description a description associated with the name, or {@code 
null} if none.
+     * @param  domainCode  the code for the domain of validity, or {@code 
null} if none.
+     * @param  scope       the scope, or {@code null} if none.
      * @param  remarks     remarks as a {@link String} or {@link 
InternationalString}, or {@code null} if none.
      * @param  deprecated  {@code true} if the object to create is deprecated.
      * @return the name together with a set of properties.
      */
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    private Map<String,Object> createProperties(final String table, String 
name, final Integer code,
-            CharSequence remarks, final boolean deprecated) throws 
SQLException, FactoryException
+    private Map<String,Object> createProperties(final String       table,
+                                                final Integer      code,
+                                                      String       name,       
 // May be replaced by an alias.
+                                                final CharSequence description,
+                                                final String       domainCode,
+                                                      String       scope,      
 // May replace "?" by text.
+                                                final CharSequence remarks,
+                                                final boolean      deprecated)
+            throws SQLException, FactoryException
     {
         /*
          * Search for aliases. Note that searching for the object code is not 
sufficient. We also need to check if the
@@ -1138,7 +1151,7 @@ codes:  for (int i=0; i<codes.length; i++) {
                     }
                 }
                 if (CharSequences.toASCII(alias).toString().equals(name)) {
-                    name = alias;
+                    name = alias;   // Same name but with accented letters.
                 } else {
                     aliases.add(owner.nameFactory.createLocalName(ns, alias));
                 }
@@ -1156,9 +1169,10 @@ codes:  for (int i=0; i<codes.length; i++) {
         if (name != null) {
             gn = owner.nameFactory.createGenericName(namespace, 
Constants.EPSG, name);
             properties.put("name", gn);
-            properties.put(NamedIdentifier.CODE_KEY,      name);
-            properties.put(NamedIdentifier.VERSION_KEY,   version);
-            properties.put(NamedIdentifier.AUTHORITY_KEY, authority);
+            properties.put(NamedIdentifier.CODE_KEY,        name);
+            properties.put(NamedIdentifier.VERSION_KEY,     version);
+            properties.put(NamedIdentifier.AUTHORITY_KEY,   authority);
+            properties.put(NamedIdentifier.DESCRIPTION_KEY, description);
             properties.put(AbstractIdentifiedObject.LOCALE_KEY, locale);
             final var id = new NamedIdentifier(properties);
             properties.clear();
@@ -1185,30 +1199,9 @@ codes:  for (int i=0; i<codes.length; i++) {
         properties.put(IdentifiedObject.REMARKS_KEY, remarks);
         properties.put(AbstractIdentifiedObject.LOCALE_KEY, locale);
         properties.put(ReferencingFactoryContainer.MT_FACTORY, 
owner.mtFactory);
-        return properties;
-    }
-
-    /**
-     * Returns the name, aliases and domain of validity for the {@link 
IdentifiedObject} to construct.
-     *
-     * @param  table       the table on which a query has been executed.
-     * @param  name        the name for the {@link IdentifiedObject} to 
construct.
-     * @param  code        the EPSG code of the object to construct.
-     * @param  domainCode  the code for the domain of validity, or {@code 
null} if none.
-     * @param  scope       the scope, or {@code null} if none.
-     * @param  remarks     remarks, or {@code null} if none.
-     * @param  deprecated  {@code true} if the object to create is deprecated.
-     * @return the name together with a set of properties.
-     */
-    private Map<String,Object> createProperties(final String table, final 
String name, final Integer code,
-            final String domainCode, String scope, final String remarks, final 
boolean deprecated)
-            throws SQLException, FactoryException
-    {
         if ("?".equals(scope)) {                // EPSG sometimes uses this 
value for unspecified scope.
             scope = null;
         }
-        @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final Map<String,Object> properties = createProperties(table, name, 
code, remarks, deprecated);
         if (domainCode != null) {
             properties.put(ObjectDomain.DOMAIN_OF_VALIDITY_KEY, 
owner.createExtent(domainCode));
         }
@@ -1344,14 +1337,14 @@ codes:  for (int i=0; i<codes.length; i++) {
         try (ResultSet result = executeQuery("Coordinate Reference System", 
"COORD_REF_SYS_CODE", "COORD_REF_SYS_NAME",
                 "SELECT COORD_REF_SYS_CODE,"          +     // [ 1]
                       " COORD_REF_SYS_NAME,"          +     // [ 2]
-                      " AREA_OF_USE_CODE,"            +     // [ 3]
+                      " AREA_OF_USE_CODE,"            +     // [ 3] Deprecated 
since EPSG version 10 (always null)
                       " CRS_SCOPE,"                   +     // [ 4]
                       " REMARKS,"                     +     // [ 5]
                       " DEPRECATED,"                  +     // [ 6]
                       " COORD_REF_SYS_KIND,"          +     // [ 7]
                       " COORD_SYS_CODE,"              +     // [ 8] Null for 
CompoundCRS
                       " DATUM_CODE,"                  +     // [ 9] Null for 
ProjectedCRS
-                      " SOURCE_GEOGCRS_CODE,"         +     // [10] For 
ProjectedCRS
+                      " BASE_CRS_CODE,"               +     // [10] For 
ProjectedCRS
                       " PROJECTION_CONV_CODE,"        +     // [11] For 
ProjectedCRS
                       " CMPD_HORIZCRS_CODE,"          +     // [12] For 
CompoundCRS only
                       " CMPD_VERTCRS_CODE"            +     // [13] For 
CompoundCRS only
@@ -1390,7 +1383,7 @@ codes:  for (int i=0; i<codes.length; i++) {
                         }
                         final EllipsoidalCS cs = 
owner.createEllipsoidalCS(csCode.toString());
                         final String datumCode = getOptionalString(result, 9);
-                        final GeodeticDatum datum;
+                        GeodeticDatum datum;
                         if (datumCode != null) {
                             datum = owner.createGeodeticDatum(datumCode);
                         } else {
@@ -1403,8 +1396,10 @@ codes:  for (int i=0; i<codes.length; i++) {
                                 endOfRecursion(GeographicCRS.class, epsg);
                             }
                         }
+                        DatumEnsemble<GeodeticDatum> ensemble = 
tryAsEnsemble(datum, GeodeticDatum.class);
+                        if (ensemble != null) datum = null;
                         crs = 
crsFactory.createGeographicCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), 
datum, cs);
+                                epsg, name, null, area, scope, remarks, 
deprecated), datum, ensemble, cs);
                         break;
                     }
                     /* 
----------------------------------------------------------------------
@@ -1478,7 +1473,7 @@ codes:  for (int i=0; i<codes.length; i++) {
                                  */
                                 
@SuppressWarnings("LocalVariableHidesMemberVariable")
                                 final Map<String,Object> properties = 
createProperties("Coordinate Reference System",
-                                                                        name, 
epsg, area, scope, remarks, deprecated);
+                                        epsg, name, null, area, scope, 
remarks, deprecated);
                                 if (baseCRS instanceof GeodeticCRS) {
                                     crs = 
crsFactory.createProjectedCRS(properties, (GeodeticCRS) baseCRS, op, cs);
                                 } else {
@@ -1496,10 +1491,12 @@ codes:  for (int i=0; i<codes.length; i++) {
                      *   VERTICAL CRS
                      * 
---------------------------------------------------------------------- */
                     case "vertical": {
-                        final VerticalCS    cs    = owner.createVerticalCS   
(getString(code, result, 8));
-                        final VerticalDatum datum = 
owner.createVerticalDatum(getString(code, result, 9));
+                        VerticalCS    cs    = owner.createVerticalCS   
(getString(code, result, 8));
+                        VerticalDatum datum = 
owner.createVerticalDatum(getString(code, result, 9));
+                        DatumEnsemble<VerticalDatum> ensemble = 
tryAsEnsemble(datum, VerticalDatum.class);
+                        if (ensemble != null) datum = null;
                         crs = 
crsFactory.createVerticalCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), 
datum, cs);
+                                epsg, name, null, area, scope, remarks, 
deprecated), datum, ensemble, cs);
                         break;
                     }
                     /* 
----------------------------------------------------------------------
@@ -1510,10 +1507,12 @@ codes:  for (int i=0; i<codes.length; i++) {
                      * 
---------------------------------------------------------------------- */
                     case "time":
                     case "temporal": {
-                        final TimeCS        cs    = owner.createTimeCS       
(getString(code, result, 8));
-                        final TemporalDatum datum = 
owner.createTemporalDatum(getString(code, result, 9));
+                        TimeCS        cs    = owner.createTimeCS       
(getString(code, result, 8));
+                        TemporalDatum datum = 
owner.createTemporalDatum(getString(code, result, 9));
+                        DatumEnsemble<TemporalDatum> ensemble = 
tryAsEnsemble(datum, TemporalDatum.class);
+                        if (ensemble != null) datum = null;
                         crs = 
crsFactory.createTemporalCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), 
datum, cs);
+                                epsg, name, null, area, scope, remarks, 
deprecated), datum, ensemble, cs);
                         break;
                     }
                     /* 
----------------------------------------------------------------------
@@ -1536,23 +1535,24 @@ codes:  for (int i=0; i<codes.length; i++) {
                         }
                         // Note: Do not invoke `createProperties` sooner.
                         crs  = 
crsFactory.createCompoundCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), 
crs1, crs2);
+                                epsg, name, null, area, scope, remarks, 
deprecated), crs1, crs2);
                         break;
                     }
                     /* 
----------------------------------------------------------------------
                      *   GEOCENTRIC CRS
                      * 
---------------------------------------------------------------------- */
                     case "geocentric": {
-                        final CoordinateSystem cs = 
owner.createCoordinateSystem(getString(code, result, 8));
-                        final GeodeticDatum datum = owner.createGeodeticDatum  
 (getString(code, result, 9));
-                        final DatumEnsemble<GeodeticDatum> datumEnsemble = 
null;  // TODO
+                        CoordinateSystem cs = 
owner.createCoordinateSystem(getString(code, result, 8));
+                        GeodeticDatum datum = owner.createGeodeticDatum   
(getString(code, result, 9));
+                        DatumEnsemble<GeodeticDatum> ensemble = 
tryAsEnsemble(datum, GeodeticDatum.class);
+                        if (ensemble != null) datum = null;
                         @SuppressWarnings("LocalVariableHidesMemberVariable")
                         final Map<String,Object> properties = 
createProperties("Coordinate Reference System",
-                                                                name, epsg, 
area, scope, remarks, deprecated);
+                                epsg, name, null, area, scope, remarks, 
deprecated);
                         if (cs instanceof CartesianCS) {
-                            crs = crsFactory.createGeodeticCRS(properties, 
datum, datumEnsemble, (CartesianCS) cs);
+                            crs = crsFactory.createGeodeticCRS(properties, 
datum, ensemble, (CartesianCS) cs);
                         } else if (cs instanceof SphericalCS) {
-                            crs = crsFactory.createGeodeticCRS(properties, 
datum, datumEnsemble, (SphericalCS) cs);
+                            crs = crsFactory.createGeodeticCRS(properties, 
datum, ensemble, (SphericalCS) cs);
                         } else {
                             throw new FactoryDataException(error().getString(
                                     Errors.Keys.IllegalCoordinateSystem_1, 
cs.getName()));
@@ -1563,21 +1563,24 @@ codes:  for (int i=0; i<codes.length; i++) {
                      *   ENGINEERING CRS
                      * 
---------------------------------------------------------------------- */
                     case "engineering": {
-                        final CoordinateSystem cs    = 
owner.createCoordinateSystem(getString(code, result, 8));
-                        final EngineeringDatum datum = 
owner.createEngineeringDatum(getString(code, result, 9));
+                        CoordinateSystem cs    = 
owner.createCoordinateSystem(getString(code, result, 8));
+                        EngineeringDatum datum = 
owner.createEngineeringDatum(getString(code, result, 9));
+                        DatumEnsemble<EngineeringDatum> ensemble = 
tryAsEnsemble(datum, EngineeringDatum.class);
+                        if (ensemble != null) datum = null;
                         crs = 
crsFactory.createEngineeringCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), 
datum, cs);
+                                epsg, name, null, area, scope, remarks, 
deprecated), datum, ensemble, cs);
                         break;
                     }
                     /* 
----------------------------------------------------------------------
                      *   PARAMETRIC CRS
                      * 
---------------------------------------------------------------------- */
                     case "parametric": {
-                        final ParametricCS    cs    = owner.createParametricCS 
  (getString(code, result, 8));
-                        final ParametricDatum datum = 
owner.createParametricDatum(getString(code, result, 9));
-                        final DatumEnsemble<ParametricDatum> datumEnsemble = 
null;  // TODO
+                        ParametricCS    cs    = owner.createParametricCS   
(getString(code, result, 8));
+                        ParametricDatum datum = 
owner.createParametricDatum(getString(code, result, 9));
+                        DatumEnsemble<ParametricDatum> ensemble = 
tryAsEnsemble(datum, ParametricDatum.class);
+                        if (ensemble != null) datum = null;
                         crs = 
crsFactory.createParametricCRS(createProperties("Coordinate Reference System",
-                                name, epsg, area, scope, remarks, deprecated), 
datum, datumEnsemble, cs);
+                                epsg, name, null, area, scope, remarks, 
deprecated), datum, ensemble, cs);
                         break;
                     }
                     /* 
----------------------------------------------------------------------
@@ -1594,6 +1597,8 @@ codes:  for (int i=0; i<codes.length; i++) {
             }
         } catch (SQLException exception) {
             throw databaseFailure(CoordinateReferenceSystem.class, code, 
exception);
+        } catch (ClassCastException exception) {
+            throw new 
FactoryDataException(error().getString(exception.getLocalizedMessage()), 
exception);
         }
         if (returnValue == null) {
              throw noSuchAuthorityCode(CoordinateReferenceSystem.class, code);
@@ -1601,6 +1606,29 @@ codes:  for (int i=0; i<codes.length; i++) {
         return returnValue;
     }
 
+    /**
+     * Returns the given datum as a datum ensemble if it should be considered 
as such.
+     * This method exists because the datum and datum ensemble are stored in 
the same table,
+     * and Apache <abbr>SIS</abbr> creates those two kinds of objects with the 
same method.
+     * The real type is resolved by inspection of the {@link 
#createDatum(String)} return value.
+     *
+     * <h4>Design restriction</h4>
+     * We cannot resolve the type with a private field which would be set by 
{@code #createDatumEnsemble(…)}
+     * because that method will not be invoked if the datum is fetched from 
the cache.
+     *
+     * @param  <D>         compile-time value of {@code memberType}.
+     * @param  datum       the datum to check if it is a datum ensemble.
+     * @param  memberType  the expected type of datum members.
+     * @return the given datum as an ensemble if it should be considered as 
such, or {@code null} otherwise.
+     * @throws ClassCastException if at least one member is not an instance of 
the specified type.
+     */
+    private static <D extends Datum> DatumEnsemble<D> tryAsEnsemble(final D 
datum, final Class<D> memberType) {
+        if (datum instanceof DatumEnsemble<?>) {
+            return DefaultDatumEnsemble.castOrCopy((DatumEnsemble<?>) 
datum).cast(memberType);
+        }
+        return null;
+    }
+
     /**
      * Creates an arbitrary datum from a code. The returned object will 
typically be an
      * instance of {@link GeodeticDatum}, {@link VerticalDatum} or {@link 
TemporalDatum}.
@@ -1610,12 +1638,12 @@ codes:  for (int i=0; i<codes.length; i++) {
      *
      * <table class="sis">
      * <caption>EPSG codes examples</caption>
-     *   <tr><th>Code</th> <th>Type</th>        <th>Description</th></tr>
-     *   <tr><td>6326</td> <td>Geodetic</td>    <td>World Geodetic System 
1984</td></tr>
-     *   <tr><td>6322</td> <td>Geodetic</td>    <td>World Geodetic System 
1972</td></tr>
-     *   <tr><td>1027</td> <td>Vertical</td>    <td>EGM2008 geoid</td></tr>
-     *   <tr><td>5100</td> <td>Vertical</td>    <td>Mean Sea Level</td></tr>
-     *   <tr><td>9315</td> <td>Engineering</td> <td>Seismic bin grid 
datum</td></tr>
+     *   <tr><th>Code</th> <th>Type</th>            <th>Description</th></tr>
+     *   <tr><td>6326</td> <td>Datum ensemble</td>  <td>World Geodetic System 
1984</td></tr>
+     *   <tr><td>6322</td> <td>Dynamic geodetic</td><td>World Geodetic System 
1972</td></tr>
+     *   <tr><td>1027</td> <td>Vertical</td>        <td>EGM2008 geoid</td></tr>
+     *   <tr><td>5100</td> <td>Vertical</td>        <td>Mean Sea 
Level</td></tr>
+     *   <tr><td>9315</td> <td>Engineering</td>     <td>Seismic bin grid 
datum</td></tr>
      * </table>
      *
      * @param  code  value allocated by EPSG.
@@ -1633,7 +1661,7 @@ codes:  for (int i=0; i<codes.length; i++) {
                       " DATUM_TYPE," +
                       " ORIGIN_DESCRIPTION," +
                       " REALIZATION_EPOCH," +
-                      " AREA_OF_USE_CODE," +
+                      " AREA_OF_USE_CODE," +        // Deprecated since EPSG 
version 10 (always null)
                       " DATUM_SCOPE," +
                       " REMARKS," +
                       " DEPRECATED," +
@@ -1653,10 +1681,9 @@ codes:  for (int i=0; i<codes.length; i++) {
                 final String  remarks    = getOptionalString (result, 8);
                 final boolean deprecated = getOptionalBoolean(result, 9);
                 @SuppressWarnings("LocalVariableHidesMemberVariable")
-                Map<String,Object> properties = createProperties("Datum", 
name, epsg, area, scope, remarks, deprecated);
-                if (anchor != null) {
-                    properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
-                }
+                Map<String,Object> properties = createProperties("Datum",
+                        epsg, name, null, area, scope, remarks, deprecated);
+                properties.put(Datum.ANCHOR_DEFINITION_KEY, anchor);
                 if (epoch != null) try {
                     /*
                      * Parse the date manually because it is declared as a 
VARCHAR instead of DATE in original
@@ -1696,6 +1723,7 @@ codes:  for (int i=0; i<codes.length; i++) {
                      * createEllipsoid(String) and 
createPrimeMeridian(String), so we must protect
                      * the properties map from changes.
                      */
+                    case "dynamic geodetic":
                     case "geodetic": {
                         properties = new HashMap<>(properties);         // 
Protect from changes
                         final Ellipsoid ellipsoid    = owner.createEllipsoid   
 (getString(code, result, 10));
@@ -1739,6 +1767,11 @@ codes:  for (int i=0; i<codes.length; i++) {
                         datum = datumFactory.createParametricDatum(properties);
                         break;
                     }
+                    case "ensemble": {
+                        properties = new HashMap<>(properties);         // 
Protect from changes
+                        datum = 
DefaultDatumEnsemble.castOrCopy(createDatumEnsemble(epsg, properties));
+                        break;
+                    }
                     default: {
                         throw new 
FactoryDataException(error().getString(Errors.Keys.UnknownType_1, type));
                     }
@@ -1757,6 +1790,44 @@ codes:  for (int i=0; i<codes.length; i++) {
         return returnValue;
     }
 
+    /**
+     * Creates an arbitrary datum ensemble from a code.
+     *
+     * @param  code        value allocated by EPSG.
+     * @param  properties  properties to assign to the datum ensemble.
+     * @return the datum ensemble for the given code, or {@code null} if not 
found.
+     */
+    private DatumEnsemble<?> createDatumEnsemble(final Integer code, final 
Map<String,Object> properties)
+            throws SQLException, FactoryException
+    {
+        double accuracy = Double.NaN;
+        try (ResultSet result = executeQuery("DatumEnsemble",
+                "SELECT ENSEMBLE_ACCURACY" +
+                " FROM \"DatumEnsemble\"" +
+                " WHERE DATUM_ENSEMBLE_CODE = ?", code))
+        {
+            // Should have exactly one value. The loop is a paranoiac safety.
+            while (result.next()) {
+                final double value = getDouble(code, result, 1);
+                if (Double.isNaN(accuracy) || value > accuracy) {
+                    accuracy = value;
+                }
+            }
+        }
+        final var members = new ArrayList<Datum>();
+        try (ResultSet result = executeQuery("DatumEnsembleMember",
+                "SELECT DATUM_CODE" +
+                " FROM \"DatumEnsembleMember\"" +
+                " WHERE DATUM_ENSEMBLE_CODE = ?" +
+                " ORDER BY DATUM_SEQUENCE", code))
+        {
+            while (result.next()) {
+                members.add(owner.createDatum(getInteger(code, result, 
1).toString()));
+            }
+        }
+        return owner.datumFactory.createDatumEnsemble(properties, members, 
PositionalAccuracyConstant.ensemble(accuracy));
+    }
+
     /**
      * Returns Bursa-Wolf parameters for a geodetic reference frame.
      * If the specified datum has no conversion information, then this method 
returns {@code null}.
@@ -1785,7 +1856,7 @@ codes:  for (int i=0; i<codes.length; i++) {
                 "SELECT COORD_OP_CODE," +
                       " COORD_OP_METHOD_CODE," +
                       " TARGET_CRS_CODE," +
-                      " AREA_OF_USE_CODE"+
+                      " AREA_OF_USE_CODE" +      // Deprecated since EPSG 
version 10 (always null).
                 " FROM \"Coordinate_Operation\"" +
                " WHERE DEPRECATED=0" +           // Do not put spaces around 
"=" - SQLTranslator searches for this exact match.
                  " AND TARGET_CRS_CODE = "       + BursaWolfInfo.TARGET_CRS +
@@ -1844,7 +1915,7 @@ codes:  for (int i=0; i<codes.length; i++) {
              * all datum seen by this method use Greenwich. But we 
nevertheless perform this check
              * as a safety for future evolution or customized EPSG dataset.
              */
-            if (!equalsIgnoreMetadata(meridian, datum.getPrimeMeridian())) {
+            if (!Utilities.equalsIgnoreMetadata(meridian, 
datum.getPrimeMeridian())) {
                 continue;
             }
             final var bwp = new BursaWolfParameters(datum, 
info.getDomainOfValidity(owner));
@@ -1931,17 +2002,17 @@ codes:  for (int i=0; i<codes.length; i++) {
                 final boolean deprecated        = getOptionalBoolean(result, 
8);
                 final Unit<Length> unit         = 
owner.createUnit(unitCode).asType(Length.class);
                 @SuppressWarnings("LocalVariableHidesMemberVariable")
-                final Map<String,Object> properties = 
createProperties("Ellipsoid", name, epsg, remarks, deprecated);
+                final Map<String,Object> properties = 
createProperties("Ellipsoid",
+                        epsg, name, null, null, null, remarks, deprecated);
                 final Ellipsoid ellipsoid;
                 if (Double.isNaN(inverseFlattening)) {
                     if (Double.isNaN(semiMinorAxis)) {
                         // Both are null, which is not allowed.
                         final String column = 
result.getMetaData().getColumnName(3);
                         throw new 
FactoryDataException(error().getString(Errors.Keys.NullValueInTable_3, code, 
column));
-                    } else {
-                        // We only have semiMinorAxis defined. It is OK
-                        ellipsoid = 
owner.datumFactory.createEllipsoid(properties, semiMajorAxis, semiMinorAxis, 
unit);
                     }
+                    // We only have semiMinorAxis defined. It is OK
+                    ellipsoid = owner.datumFactory.createEllipsoid(properties, 
semiMajorAxis, semiMinorAxis, unit);
                 } else {
                     if (!Double.isNaN(semiMinorAxis)) {
                         // Both `inverseFlattening` and `semiMinorAxis` are 
defined.
@@ -2014,7 +2085,8 @@ codes:  for (int i=0; i<codes.length; i++) {
                 final boolean deprecated = getOptionalBoolean(result, 6);
                 final Unit<Angle> unit = 
owner.createUnit(unitCode).asType(Angle.class);
                 final PrimeMeridian primeMeridian = 
owner.datumFactory.createPrimeMeridian(
-                        createProperties("Prime Meridian", name, epsg, 
remarks, deprecated), longitude, unit);
+                        createProperties("Prime Meridian", epsg, name, null, 
null, null, remarks, deprecated),
+                        longitude, unit);
                 returnValue = ensureSingleton(primeMeridian, returnValue, 
code);
             }
         } catch (SQLException exception) {
@@ -2039,6 +2111,15 @@ codes:  for (int i=0; i<codes.length; i++) {
      *   <tr><td>3391</td> <td>World - between 80°S and 84°N</td></tr>
      * </table>
      *
+     * <h4>History</h4>
+     * The table name was {@code "Area"} before version 10 of the 
<abbr>EPSG</abbr> geodetic dataset.
+     * Starting from <abbr>EPSG</abbr> version 10, the table name is {@code 
"Extent"} but the first 7
+     * columns are the same with different names and order. The last columns 
are news.
+     *
+     * <p>Before <abbr>EPSG</abbr> version 10, extents were referenced in 
columns named {@code AREA_OF_USE_CODE}.
+     * Starting with version 10, that column still exists but is deprecated 
and contains only {@code null} values.
+     * An {@code "Usage"} intersection table is used instead.</p>
+     *
      * @param  code  value allocated by EPSG.
      * @return the extent for the given code.
      * @throws NoSuchAuthorityCodeException if the specified {@code code} was 
not found.
@@ -2153,7 +2234,8 @@ codes:  for (int i=0; i<codes.length; i++) {
                 final boolean deprecated = getOptionalBoolean(result, 6);
                 final CoordinateSystemAxis[] axes = 
createCoordinateSystemAxes(epsg, dimension);
                 @SuppressWarnings("LocalVariableHidesMemberVariable")
-                final Map<String,Object> properties = 
createProperties("Coordinate System", name, epsg, remarks, deprecated);   // 
Must be after axes.
+                final Map<String,Object> properties = 
createProperties("Coordinate System",
+                        epsg, name, null, null, null, remarks, deprecated);   
// Must be after axes.
                 /*
                  * The following switch statement should have a case for all 
"epsg_cs_kind" values enumerated
                  * in the "EPSG_Prepare.sql" file, except that the values in 
this Java code are in lower cases.
@@ -2335,7 +2417,7 @@ codes:  for (int i=0; i<codes.length; i++) {
                 }
                 final AxisName an = getAxisName(nameCode);
                 final CoordinateSystemAxis axis = 
owner.csFactory.createCoordinateSystemAxis(
-                        createProperties("Coordinate Axis", an.name, epsg, 
an.description, false),
+                        createProperties("Coordinate Axis", epsg, an.name, 
an.description, null, null, an.remarks, false),
                         abbreviation, direction, owner.createUnit(unit));
                 returnValue = ensureSingleton(axis, returnValue, code);
             }
@@ -2365,12 +2447,7 @@ codes:  for (int i=0; i<codes.length; i++) {
                     final String name  = getString(code,   result, 1);
                     String description = getOptionalString(result, 2);
                     String remarks     = getOptionalString(result, 3);
-                    if (description == null) {
-                        description = remarks;
-                    } else if (remarks != null) {
-                        description += System.lineSeparator() + remarks;
-                    }
-                    final AxisName axis = new AxisName(name, description);
+                    final var axis = new AxisName(name, description, remarks);
                     returnValue = ensureSingleton(axis, returnValue, code);
                 }
             }
@@ -2597,8 +2674,8 @@ next:                   while (r.next()) {
                                     Double.POSITIVE_INFINITY, false, 
CollectionsExt.first(units)); break;
                 }
                 @SuppressWarnings("LocalVariableHidesMemberVariable")
-                final Map<String,Object> properties =
-                        createProperties("Coordinate_Operation Parameter", 
name, epsg, isReversible, deprecated);
+                final Map<String,Object> properties = 
createProperties("Coordinate_Operation Parameter",
+                        epsg, name, null, null, null, isReversible, 
deprecated);
                 properties.put(Identifier.DESCRIPTION_KEY, description);
                 final var descriptor = new 
DefaultParameterDescriptor<>(properties, 1, 1, type, valueDomain, null, null);
                 returnValue = ensureSingleton(descriptor, returnValue, code);
@@ -2761,7 +2838,8 @@ next:                   while (r.next()) {
                 final boolean deprecated = getOptionalBoolean(result, 4);
                 final ParameterDescriptor<?>[] descriptors = 
createParameterDescriptors(epsg);
                 @SuppressWarnings("LocalVariableHidesMemberVariable")
-                Map<String,Object> properties = 
createProperties("Coordinate_Operation Method", name, epsg, remarks, 
deprecated);
+                final Map<String,Object> properties = 
createProperties("Coordinate_Operation Method",
+                        epsg, name, null, null, null, remarks, deprecated);
                 /*
                  * Note: we do not store the formula at this time, because the 
text is very verbose and rarely used.
                  */
@@ -2816,7 +2894,7 @@ next:                   while (r.next()) {
                           " COORD_OP_METHOD_CODE," +
                           " COORD_TFM_VERSION," +
                           " COORD_OP_ACCURACY," +
-                          " AREA_OF_USE_CODE," +
+                          " AREA_OF_USE_CODE," +    // Deprecated since EPSG 
version 10 (always null)
                           " COORD_OP_SCOPE," +
                           " REMARKS," +
                           " DEPRECATED" +
@@ -2894,12 +2972,10 @@ next:                   while (r.next()) {
                      *       overwrite the properties map.
                      */
                     Map<String,Object> opProperties = 
createProperties("Coordinate_Operation",
-                            name, epsg, area, scope, remarks, deprecated);
+                            epsg, name, null, area, scope, remarks, 
deprecated);
                     
opProperties.put(CoordinateOperation.OPERATION_VERSION_KEY, version);
-                    if (!Double.isNaN(accuracy)) {
-                        
opProperties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
-                                         
PositionalAccuracyConstant.create(accuracy));
-                    }
+                    
opProperties.put(CoordinateOperation.COORDINATE_OPERATION_ACCURACY_KEY,
+                                     
PositionalAccuracyConstant.transformation(accuracy));
                     /*
                      * Creates the operation. Conversions should be the only 
operations allowed to have
                      * null source and target CRS. In such case, the operation 
is a defining conversion
@@ -3033,7 +3109,7 @@ next:                   while (r.next()) {
             do {
                 /*
                  * This `do` loop is executed twice: the first time for 
searching defining conversions, and the second
-                 * time for searching all other kind of operations. Defining 
conversions are searched first because
+                 * time for searching all other kinds of operations. Defining 
conversions are searched first because
                  * they are, by definition, the most accurate operations.
                  */
                 final String key, sql;
@@ -3053,7 +3129,7 @@ next:                   while (r.next()) {
                     key = "ConversionFromCRS";
                     sql = "SELECT PROJECTION_CONV_CODE" +
                           " FROM \"Coordinate Reference System\"" +
-                          " WHERE SOURCE_GEOGCRS_CODE = ?" +
+                          " WHERE BASE_CRS_CODE = ?" +
                             " AND COORD_REF_SYS_CODE = ?";
                 }
                 final Integer targetKey = searchTransformations ? null : 
pair[1];
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Finish.sql
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Finish.sql
index 1250e6e24a..2c28d81c73 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Finish.sql
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/EPSG_Finish.sql
@@ -75,7 +75,7 @@ CREATE INDEX ix_alias               ON epsg_alias             
        (object_ta
 -- Indexes used by EPSGDataAccess.Finder for reverse operation.            --
 -----------------------------------------------------------------------------
 CREATE INDEX ix_major_ellipsoid ON epsg_ellipsoid                 
(semi_major_axis);
-CREATE INDEX ix_geogcrs_crs     ON epsg_coordinatereferencesystem 
(source_geogcrs_code);
+CREATE INDEX ix_geogcrs_crs     ON epsg_coordinatereferencesystem 
(base_crs_code);
 CREATE INDEX ix_ellipsoid_datum ON epsg_datum                     
(ellipsoid_code);
 
 
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
index 0f28c94563..d3c836baf9 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/factory/sql/SQLTranslator.java
@@ -17,6 +17,7 @@
 package org.apache.sis.referencing.factory.sql;
 
 import java.util.Map;
+import java.util.HashMap;
 import java.util.Locale;
 import java.util.function.Function;
 import java.sql.DatabaseMetaData;
@@ -35,17 +36,17 @@ import org.apache.sis.referencing.internal.Resources;
 
 
 /**
- * Adapter of <abbr>SQL</abbr> statements for variations in syntax and table 
names.
+ * Translator of <abbr>SQL</abbr> statements for variations in schema, table 
and column names.
  * The {@link #apply(String)} method is invoked when a new {@link 
PreparedStatement}
  * is about to be created from a <abbr>SQL</abbr> string.
- * This class allows {@link EPSGFactory} to accept some variants of table 
names.
+ * That method can modify the string before execution.
  * For example, the following <abbr>SQL</abbr> query:
  *
  * <ul>
  *   <li>{@code SELECT * FROM "Coordinate Reference System"}</li>
  * </ul>
  *
- * may be converted to one of the following possibilities:
+ * can be translated to one of the following possibilities:
  *
  * <ul>
  *   <li>{@code SELECT * FROM "Coordinate Reference System"} (no change)</li>
@@ -53,55 +54,65 @@ import org.apache.sis.referencing.internal.Resources;
  *   <li>{@code SELECT * FROM epsg_coordinatereferencesystem}</li>
  * </ul>
  *
- * The mixed-case variant is used in the dataset distributed by 
<abbr>EPSG</abbr> in the MS-Access format,
- * while the lower-case variant is used in the <abbr>SQL</abbr> scripts 
distributed by <abbr>EPSG</abbr>.
- * By default, this class auto-detects which database schema contains the 
<abbr>EPSG</abbr> tables
- * and whether the database uses the lower-case or mixed-case variant of table 
names.
- * It is legal to use the mixed-case variant in a PostgreSQL database (for 
example)
- * even if <abbr>EPSG</abbr> distributes the PosthreSQL scripts in lower-case.
- * The following table gives the mapping between the two variants accepted by 
{@link EPSGFactory}:
+ * Above possibilities differ in the letter cases, spaces and {@code "epsg_"} 
prefix (non-exhaustive).
+ * Those differences exist between the <abbr>EPSG</abbr> databases distributed 
in MS-Access format or
+ * as <abbr>SQL</abbr> scripts. More differences may exist for handling the 
differences between version
+ * 9 and 10 of the <abbr>EPSG</abbr> database schema.
+ *
+ * <p>Apache <abbr>SIS</abbr> generally uses the naming convention found in 
the MS-Access database,
+ * because it provides more readable <abbr>SQL</abbr> statements. 
<abbr>SIS</abbr> also assumes an
+ * <abbr>EPSG</abbr> database schema version 10 or latter. If {@link 
EPSGFactory} is connected to
+ * a database which uses a different table naming convention, or to 
<abbr>EPSG</abbr> version 9,
+ * then this {@code SQLTranslator} class will translate the <abbr>SQL</abbr> 
statements on-the-fly.
+ * The following table gives the mapping between the two naming 
conventions:</p>
  *
  * <table class="sis">
  *   <caption>Table and column names</caption>
- *   <tr><th>Element</th><th>Name in MS-Access database</th>                   
 <th>Name in <abbr>SQL</abbr> scripts</th></tr>
- *   <tr><td>Table</td>  <td>{@code Alias}</td>                                
 <td>{@code epsg_alias}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Area}</td>                                 
 <td>{@code epsg_area}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Change}</td>                               
 <td>{@code epsg_change}</td></tr>
- *   <tr><td>Table</td>  <td>{@code ConventionalRS}</td>                       
 <td>{@code epsg_conventionalrs}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate Axis}</td>                      
 <td>{@code epsg_coordinateaxis}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate Axis Name}</td>                 
 <td>{@code epsg_coordinateaxisname}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate_Operation}</td>                 
 <td>{@code epsg_coordoperation}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate_Operation Method}</td>          
 <td>{@code epsg_coordoperationmethod}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate_Operation Parameter}</td>       
 <td>{@code epsg_coordoperationparam}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate_Operation Parameter Usage}</td> 
 <td>{@code epsg_coordoperationparamusage}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate_Operation Parameter Value}</td> 
 <td>{@code epsg_coordoperationparamvalue}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate_Operation Path}</td>            
 <td>{@code epsg_coordoperationpath}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate Reference System}</td>          
 <td>{@code epsg_coordinatereferencesystem}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Coordinate System}</td>                    
 <td>{@code epsg_coordinatesystem}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Datum}</td>                                
 <td>{@code epsg_datum}</td></tr>
- *   <tr><td>Table</td>  <td>{@code DatumEnsemble}</td>                        
 <td>{@code epsg_datumensemble}</td></tr>
- *   <tr><td>Table</td>  <td>{@code DatumEnsembleMember}</td>                  
 <td>{@code epsg_datumensemblemember}</td></tr>
- *   <tr><td>Table</td>  <td>{@code DatumRealizationMethod}</td>               
 <td>{@code epsg_datumrealizationmethod}</td></tr>
- *   <tr><td>Table</td>  <td>{@code DefiningOperation}</td>                    
 <td>{@code epsg_definingoperation}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Deprecation}</td>                          
 <td>{@code epsg_deprecation}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Ellipsoid}</td>                            
 <td>{@code epsg_ellipsoid}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Extent}</td>                               
 <td>{@code epsg_extent}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Naming System}</td>                        
 <td>{@code epsg_namingsystem}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Prime Meridian}</td>                       
 <td>{@code epsg_primemeridian}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Scope}</td>                                
 <td>{@code epsg_scope}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Supersession}</td>                         
 <td>{@code epsg_supersession}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Unit of Measure}</td>                      
 <td>{@code epsg_unitofmeasure}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Version History}</td>                      
 <td>{@code epsg_versionhistory}</td></tr>
- *   <tr><td>Table</td>  <td>{@code Usage}</td>                                
 <td>{@code epsg_usage}</td></tr>
- *   <tr><td>Column</td> <td>{@code ORDER}</td>                                
 <td>{@code coord_axis_order}</td></tr>
+ *   <tr><th>Element</th>       <th>Name in MS-Access database</th>            
        <th>Name in <abbr>SQL</abbr> scripts</th></tr>
+ *   <tr><td>Table</td>         <td>{@code Alias}</td>                         
        <td>{@code epsg_alias}</td></tr>
+ *   <tr><td>Legacy table</td>  <td>{@code Area}</td>                          
        <td>{@code epsg_area}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Change}</td>                        
        <td>{@code epsg_change}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code ConventionalRS}</td>                
        <td>{@code epsg_conventionalrs}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate Axis}</td>               
        <td>{@code epsg_coordinateaxis}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate Axis Name}</td>          
        <td>{@code epsg_coordinateaxisname}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate_Operation}</td>          
        <td>{@code epsg_coordoperation}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate_Operation Method}</td>   
        <td>{@code epsg_coordoperationmethod}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate_Operation 
Parameter}</td>        <td>{@code epsg_coordoperationparam}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate_Operation Parameter 
Usage}</td>  <td>{@code epsg_coordoperationparamusage}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate_Operation Parameter 
Value}</td>  <td>{@code epsg_coordoperationparamvalue}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate_Operation Path}</td>     
        <td>{@code epsg_coordoperationpath}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate Reference System}</td>   
        <td>{@code epsg_coordinatereferencesystem}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Coordinate System}</td>             
        <td>{@code epsg_coordinatesystem}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Datum}</td>                         
        <td>{@code epsg_datum}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code DatumEnsemble}</td>                 
        <td>{@code epsg_datumensemble}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code DatumEnsembleMember}</td>           
        <td>{@code epsg_datumensemblemember}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code DatumRealizationMethod}</td>        
        <td>{@code epsg_datumrealizationmethod}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code DefiningOperation}</td>             
        <td>{@code epsg_definingoperation}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Deprecation}</td>                   
        <td>{@code epsg_deprecation}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Ellipsoid}</td>                     
        <td>{@code epsg_ellipsoid}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Extent}</td>                        
        <td>{@code epsg_extent}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Naming System}</td>                 
        <td>{@code epsg_namingsystem}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Prime Meridian}</td>                
        <td>{@code epsg_primemeridian}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Scope}</td>                         
        <td>{@code epsg_scope}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Supersession}</td>                  
        <td>{@code epsg_supersession}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Unit of Measure}</td>               
        <td>{@code epsg_unitofmeasure}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Version History}</td>               
        <td>{@code epsg_versionhistory}</td></tr>
+ *   <tr><td>Table</td>         <td>{@code Usage}</td>                         
        <td>{@code epsg_usage}</td></tr>
+ *   <tr><td>Column</td>        <td>{@code ORDER}</td>                         
        <td>{@code coord_axis_order}</td></tr>
  * </table>
  *
+ * Apache <abbr>SIS</abbr> automatically detects which convention is used, 
regardless the database engine.
+ * For example, it is legal to use the mixed-case variant in a PostgreSQL 
database
+ * even if <abbr>EPSG</abbr> distributes the PostgreSQL scripts in lower-case.
+ * The {@code "epsg_"} prefix is redundant with database schema and can be 
omitted.
+ * {@code SQLTranslator} automatically detects which database schema contains 
the <abbr>EPSG</abbr> tables.
+ *
  * <h2>Thread safety</h2>
  * All {@code SQLTranslator} instances given to the {@link EPSGFactory} 
constructor
  * <strong>shall</strong> be immutable and thread-safe.
  *
  * @author  Rueben Schulz (UBC)
- * @author  Martin Desruisseaux (IRD)
+ * @author  Martin Desruisseaux (IRD, Geomatys)
  * @author  Didier Richard (IGN)
  * @author  John Grange
  * @version 1.5
@@ -272,17 +283,21 @@ public class SQLTranslator implements 
Function<String,String> {
     }
 
     /**
-     * Sets the value of all non-final fields. This method performs two steps:
+     * Sets the value of all non-final fields. This method performs the 
following steps:
      *
      * <ol class="verbose">
-     *   <li>Find the schema that seems to contain the EPSG tables. If there 
is more than one schema containing the
-     *       tables, gives precedence to the schema named "EPSG" if one is 
found. If there is no schema named "EPSG",
-     *       take an arbitrary schema. It may be the empty string if the 
tables are not contained in a schema.</li>
+     *   <li>Find the schema that seems to contain the <abbr>EPSG</abbr> 
tables.
+     *       If there is more than one schema containing the tables, give 
precedence to the schema named
+     *       {@code "EPSG"} (ignoring case), or an arbitrary schema if none 
has been found with that name.
+     *       The schema name may be the empty string if the tables are not 
contained in a schema.</li>
+     *
+     *   <li>Determine whether the table names are prefixed by {@value 
#TABLE_PREFIX}
+     *       and whether table names are in lower-case or mixed-case.</li>
      *
      *   <li>Fill the {@link #tableRenaming} and {@link #columnRenaming} maps. 
These maps translate table
      *       and column names used in the <abbr>SQL</abbr> statements into the 
names used by the database.
      *       Two conventions are understood: the names used in the MS-Access 
database or the names used
-     *       in the <abbr>SQL</abbr> scripts. Both of them are distributed by 
<abbr>EPSG</abbr>.</li>
+     *       in the <abbr>SQL</abbr> scripts, potentially with {@linkplain 
#TABLE_PREFIX prefix} removed.</li>
      * </ol>
      */
     final void setup(final DatabaseMetaData md) throws SQLException {
@@ -317,31 +332,62 @@ public class SQLTranslator implements 
Function<String,String> {
         /*
          * At this point, the catalog and schema have been found or have been 
confirmed,
          * or are still null if we did not found the EPSG table used as a 
sentinel value.
+         * The following map contains renaming not covered by the generic 
algorithm
+         * implemented in `toActualTableName(…)`.
          */
         if (!useMixedCaseTableNames) {
             tableRenaming = Map.of("Coordinate_Operation", "coordoperation",
                                    "Parameter",            "param");
         }
+        /*
+         * Column name patterns which will be used in the rest of this method.
+         * They need to be adapted to the letter case convention of the 
database.
+         */
+        String order       = "ORDER";
+        String baseCRS     = "%CRS_CODE";   // "BASE_CRS_CODE" or 
"SOURCE_GEOGCRS_CODE".
+        String deprecated  = "DEPRECATED";
+        String objectTable = ENUMERATION_COLUMN;
+        if (md.storesLowerCaseIdentifiers()) {
+            order       =       order.toLowerCase(Locale.US);
+            baseCRS     =     baseCRS.toLowerCase(Locale.US);
+            deprecated  =  deprecated.toLowerCase(Locale.US);
+            objectTable = objectTable.toLowerCase(Locale.US);
+        }
         /*
          * MS-Access database uses a column named "ORDER" in the "Coordinate 
Axis" table.
          * This column has been renamed "coord_axis_order" in DLL scripts.
          * We need to check which name our current database uses.
          */
-        try (ResultSet result = md.getColumns(catalog, schemaPattern, 
toActualTableName("Coordinate Axis"), "ORDER")) {
+        try (ResultSet result = md.getColumns(catalog, schemaPattern, 
toActualTableName("Coordinate Axis"), order)) {
             if (result.next()) {
                 columnRenaming = Map.of("COORD_AXIS_ORDER", "ORDER");
             }
         }
+        /*
+         * A column named "BASE_CRS_CODE" in EPSG version 9 has been renamed 
"SOURCE_GEOGCRS_CODE" in version 10.
+         * Detects which name is used, with precedence to latest database 
version if the two columns are found.
+         */
+skip:   try (ResultSet result = md.getColumns(catalog, schemaPattern, 
toActualTableName("Coordinate Reference System"), baseCRS)) {
+            boolean isOldSchema = false;
+            while (result.next()) {
+                final String column = result.getString(Reflection.COLUMN_NAME);
+                if ("BASE_CRS_CODE".equalsIgnoreCase(column)) {
+                    break skip;     // Found the column of EPSG version 10.
+                }
+                if (!isOldSchema) {
+                    isOldSchema = 
"SOURCE_GEOGCRS_CODE".equalsIgnoreCase(column);
+                }
+            }
+            if (isOldSchema) {
+                columnRenaming = new HashMap<>(columnRenaming);
+                columnRenaming.put("BASE_CRS_CODE", "SOURCE_GEOGCRS_CODE");
+                columnRenaming = Map.copyOf(columnRenaming);
+            }
+        }
         /*
          * Detect if the database uses boolean types where applicable.
          * We arbitrarily use the Datum table as a representative value.
          */
-        String deprecated  = "DEPRECATED";
-        String objectTable = ENUMERATION_COLUMN;
-        if (md.storesLowerCaseIdentifiers()) {
-            deprecated  =  deprecated.toLowerCase(Locale.US);
-            objectTable = objectTable.toLowerCase(Locale.US);
-        }
         final String tablePattern = usePrefixedTableNames ? 
SQLUtilities.escape(TABLE_PREFIX, escape) + '%' : null;
         try (ResultSet result = md.getColumns(catalog, schemaPattern, 
tablePattern, deprecated)) {
             while (result.next()) {
@@ -436,13 +482,12 @@ public class SQLTranslator implements 
Function<String,String> {
     /**
      * Converts a mixed-case table name to the convention used in the database.
      * The names of the tables for the two conventions are listed in a table 
in the Javadoc of this class.
-     * The rreturned string does not include the identifier quotes.
+     * The returned string does not include the identifier quotes.
      *
      * @param  name  the mixed-case table name.
      * @return the name converted to the convention used by the database.
-     * @since 1.5
      */
-    public final String toActualTableName(String name) {
+    final String toActualTableName(String name) {
         if (useMixedCaseTableNames) {
             return name;
         }
diff --git 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
index 123aea11c2..50307e14c7 100644
--- 
a/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
+++ 
b/endorsed/src/org.apache.sis.referencing/main/org/apache/sis/referencing/internal/PositionalAccuracyConstant.java
@@ -126,6 +126,12 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
      */
     public static final PositionalAccuracy SAME_DATUM_ENSEMBLE;
 
+    /**
+     * Name for accuracy metadata of datum ensemble.
+     */
+    private static final DefaultMeasureReference ENSEMBLE_REFERENCE =
+            new 
DefaultMeasureReference(Vocabulary.formatInternational(Vocabulary.Keys.EnsembleAccuracy));
+
     /**
      * Name for accuracy metadata of coordinate transformations.
      */
@@ -138,13 +144,14 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
      * compared by the database maintainers against some results taken as 
true. By contrast, the accuracies that
      * we have set to conservative values are considered "direct internal".
      */
-    private static final DefaultEvaluationMethod TRANSFORMATION_METHOD =
+    private static final DefaultEvaluationMethod EVALUATION_METHOD =
             new DefaultEvaluationMethod(EvaluationMethodType.DIRECT_EXTERNAL,
                     
Resources.formatInternational(Resources.Keys.AccuracyFromGeodeticDatase));
 
     static {
+        ENSEMBLE_REFERENCE      
.transitionTo(DefaultMeasureReference.State.FINAL);
         
TRANSFORMATION_REFERENCE.transitionTo(DefaultMeasureReference.State.FINAL);
-        TRANSFORMATION_METHOD   
.transitionTo(DefaultEvaluationMethod.State.FINAL);
+        EVALUATION_METHOD       
.transitionTo(DefaultEvaluationMethod.State.FINAL);
         final var desc   = 
Resources.formatInternational(Resources.Keys.ConformanceMeansDatumShift);
         final var method = new 
DefaultEvaluationMethod(EvaluationMethodType.DIRECT_INTERNAL, desc);
         final var pass   = new DefaultConformanceResult(Citations.SIS, desc, 
true);
@@ -193,27 +200,38 @@ public final class PositionalAccuracyConstant extends 
DefaultAbsoluteExternalPos
     }
 
     /**
-     * Creates a positional accuracy for a value specified in the EPSG 
database.
+     * Creates a transformation accuracy for the given value, in metres.
+     * This method may return a cached value.
      *
-     * @param  accuracy  the linear accuracy in metres.
+     * @param  accuracy  the accuracy in metres.
+     * @return a positional accuracy with the given value, or {@code null} if 
the value is not positive.
      */
-    private PositionalAccuracyConstant(final Double accuracy) {
-        this(TRANSFORMATION_REFERENCE, TRANSFORMATION_METHOD, null, accuracy);
+    public static PositionalAccuracy transformation(final double accuracy) {
+        if (accuracy >= 0) {
+            return CACHE.computeIfAbsent(Math.abs(accuracy),    // For making 
sure that we have positive zero.
+                    (key) -> new 
PositionalAccuracyConstant(TRANSFORMATION_REFERENCE, EVALUATION_METHOD, null, 
key));
+        }
+        return null;
     }
 
     /**
-     * Creates a positional accuracy for the given value, in metres.
+     * Creates a datum ensemble accuracy for the given value, in metres.
      * This method may return a cached value.
      *
      * @param  accuracy  the accuracy in metres.
-     * @return a positional accuracy with the given value.
+     * @return a positional accuracy with the given value, or {@code null} if 
the value is not positive.
      */
-    public static PositionalAccuracy create(final Double accuracy) {
-        return CACHE.computeIfAbsent(accuracy, 
PositionalAccuracyConstant::new);
+    public static PositionalAccuracy ensemble(final double accuracy) {
+        if (accuracy >= 0) {
+            return CACHE.computeIfAbsent(-Math.abs(accuracy),    // For making 
sure that we have negative zero.
+                    (key) -> new 
PositionalAccuracyConstant(ENSEMBLE_REFERENCE, EVALUATION_METHOD, null, -key));
+        }
+        return null;
     }
 
     /**
-     * Cache the positional accuracies of coordinate transformations.
+     * Cache of positional accuracies of coordinate transformations (positive) 
or datum ensembles (negative).
+     * The sign is used for differentiating whether the cache value is for an 
ensemble or a transformation.
      * Most coordinate operations use a small set of accuracy values.
      */
     private static final WeakValueHashMap<Double,PositionalAccuracy> CACHE = 
new WeakValueHashMap<>(Double.class);
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
index 9964ee5081..4bb8178aaf 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.java
@@ -489,6 +489,11 @@ public class Vocabulary extends IndexedResourceBundle {
          */
         public static final short Engineering = 77;
 
+        /**
+         * Ensemble accuracy
+         */
+        public static final short EnsembleAccuracy = 278;
+
         /**
          * {0} entr{0,choice,0#y|2#ies}
          */
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
index 5c17e977e3..5541020861 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary.properties
@@ -103,6 +103,7 @@ EndDate                 = End date
 EndPoint                = End point
 Engineering             = Engineering
 EntryCount_1            = {0} entr{0,choice,0#y|2#ies}
+EnsembleAccuracy        = Ensemble accuracy
 Envelope                = Envelope
 Errors                  = Errors
 Extent                  = Extent
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
index e7d786b763..c51e88a567 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/resources/Vocabulary_fr.properties
@@ -110,6 +110,7 @@ EndDate                 = Date de fin
 EndPoint                = Point d\u2019arriv\u00e9
 Engineering             = Ing\u00e9nierie
 EntryCount_1            = {0} entr\u00e9e{0,choice,0#|2#s}
+EnsembleAccuracy        = Pr\u00e9cision de l\u2019ensemble
 Envelope                = Enveloppe
 Errors                  = Erreurs
 Extent                  = \u00c9tendue
diff --git 
a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
 
b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
index fa51bec083..b9aa24ba56 100644
--- 
a/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
+++ 
b/optional/src/org.apache.sis.referencing.epsg/test/org/apache/sis/referencing/factory/sql/epsg/DebugTools.sql
@@ -44,7 +44,7 @@ CREATE VIEW "OperationMethod dimension" AS
        (SELECT COORD_OP_METHOD_CODE, COORD_OP_TYPE, FALSE AS IS_CONVERSION, 
SOURCE_CRS_CODE, TARGET_CRS_CODE
           FROM "Coordinate_Operation" WHERE SOURCE_CRS_CODE IS NOT NULL OR 
TARGET_CRS_CODE IS NOT NULL
      UNION
-        SELECT COORD_OP_METHOD_CODE, COORD_OP_TYPE, TRUE AS IS_CONVERSION, 
SOURCE_GEOGCRS_CODE AS SOURCE_CRS_CODE, COORD_REF_SYS_CODE AS TARGET_CRS_CODE
+        SELECT COORD_OP_METHOD_CODE, COORD_OP_TYPE, TRUE AS IS_CONVERSION, 
BASE_CRS_CODE AS SOURCE_CRS_CODE, COORD_REF_SYS_CODE AS TARGET_CRS_CODE
           FROM "Coordinate Reference System" AS CRS INNER JOIN 
"Coordinate_Operation" AS CO ON CRS.PROJECTION_CONV_CODE = CO.COORD_OP_CODE) AS 
P
 
      LEFT JOIN "CRS dimension" AS DS ON DS.COORD_REF_SYS_CODE = 
P.SOURCE_CRS_CODE

Reply via email to