2020package org .apache .cassandra .bridge ;
2121
2222import java .util .Arrays ;
23+ import java .util .Collections ;
2324import java .util .HashSet ;
25+ import java .util .List ;
2426import java .util .Optional ;
2527import java .util .Set ;
2628
4547 */
4648public enum CassandraVersion
4749{
48- THREEZERO (30 , "3.0" , "three-zero" , "big" ),
49- FOURZERO (40 , "4.0" , "four-zero" , "big" ),
50- FOURONE (41 , "4.1" , "four-zero" , "big" ),
51- FIVEZERO (50 , "5.0" , "five-zero" , "big" , "bti" );
50+ THREEZERO (30 , "3.0" , "three-zero" , new String []{"big" },
51+ new String []{
52+ // Cassandra 3.x native sstable versions
53+ "big-ma" ,
54+ "big-mb" ,
55+ "big-mc" ,
56+ "big-md" ,
57+ "big-me" ,
58+ "big-mf"
59+ }),
60+ FOURZERO (40 , "4.0" , "four-zero" , new String []{"big" },
61+ new String []{
62+ // Cassandra 4.0 native sstable versions
63+ "big-na" ,
64+ "big-nb" ,
65+ }),
66+ FOURONE (41 , "4.1" , "four-zero" , new String []{"big" },
67+ new String []{
68+ // Cassandra 4.1 did not introduce new native SSTable versions
69+ }),
70+ FIVEZERO (50 , "5.0" , "five-zero" , new String []{"big" , "bti" },
71+ new String []{
72+ // Cassandra 5.0 native sstable versions
73+ "big-oa" ,
74+ "bti-da" ,
75+ });
5276
5377 private final int number ;
5478 private final String name ;
5579 private final String jarBaseName ; // Must match shadowJar.archiveFileName from Gradle configuration (without extension)
5680 private final Set <String > sstableFormats ;
81+ private final List <String > nativeSStableVersions ;
5782
58- CassandraVersion (int number , String name , String jarBaseName , String ... sstableFormats )
83+
84+ CassandraVersion (int number , String name , String jarBaseName , String [] sstableFormats , String [] nativeSStableVersions )
5985 {
6086 this .number = number ;
6187 this .name = name ;
6288 this .jarBaseName = jarBaseName ;
6389 this .sstableFormats = new HashSet <>(Arrays .asList (sstableFormats ));
90+ this .nativeSStableVersions = List .of (nativeSStableVersions );
6491 }
6592
6693 public int versionNumber ()
@@ -78,13 +105,89 @@ public String jarBaseName()
78105 return jarBaseName ;
79106 }
80107
81- private static final String sstableFormat ;
108+ /**
109+ * Get the set of SSTable formats supported by this Cassandra version.
110+ *
111+ * @return Set of supported SSTable format strings
112+ */
113+ public Set <String > sstableFormats ()
114+ {
115+ return sstableFormats ;
116+ }
117+
118+ /**
119+ * Get the list of native SSTable version strings for this Cassandra version.
120+ *
121+ * @return List of native SSTable version strings
122+ */
123+ public List <String > getNativeSStableVersions ()
124+ {
125+ return nativeSStableVersions ;
126+ }
127+
128+ /**
129+ * Get the set of SSTable version strings that this Cassandra version can read.
130+ * This includes:
131+ * - Native versions for this Cassandra version
132+ * - All SSTable versions from the previous major version (including all minor versions)
133+ * For example, Cassandra 5.0 can read:
134+ * - 5.0 native versions (big-oa, bti-da)
135+ * - 4.0 versions (big-na, big-nb)
136+ * - 4.1 versions (if any)
137+ * But NOT 3.0 versions
138+ *
139+ * @return Set of full SSTable version strings that can be read
140+ */
141+ public Set <String > getSupportedSStableVersionsForRead ()
142+ {
143+ Set <String > readableVersions = new HashSet <>(this .nativeSStableVersions );
144+
145+ int previousMajor = getPreviousMajorVersion ();
146+
147+ // Add all SSTable versions from the previous major version and its minors
148+ // E.g., C* 5.0 (version 50) can read C* 4.0 (40) and C* 4.1 (41) SSTables, but not C* 3.x (30)
149+ for (CassandraVersion version : CassandraVersion .values ())
150+ {
151+ // Include versions from the previous major version family (e.g., 40-49 for C* 5.0)
152+ if (version .versionNumber () >= previousMajor && version .versionNumber () < this .number )
153+ {
154+ readableVersions .addAll (version .nativeSStableVersions );
155+ }
156+ }
157+
158+ return Collections .unmodifiableSet (readableVersions );
159+ }
160+
161+ /**
162+ * Get the previous major version number for this Cassandra version.
163+ * Calculates dynamically using: (majorVersion - 1) * 10
164+ * For example:
165+ * - C5.0 (50) returns 40 (C4.x)
166+ * - C4.1 (41) returns 30 (C3.x)
167+ * - C4.0 (40) returns 30 (C3.x)
168+ * - C3.0 (30) returns 20 (C2.x - which doesn't exist)
169+ * - C10.0 (100) returns 90 (C9.x)
170+ *
171+ * @return previous major version number
172+ */
173+ @ VisibleForTesting
174+ int getPreviousMajorVersion ()
175+ {
176+ // Get major version: 50 -> 5, 41 -> 4, 40 -> 4, 30 -> 3
177+ int majorVersion = this .number / 10 ;
178+
179+ // Calculate previous major version: (majorVersion - 1) * 10
180+ // E.g., 5 -> 40, 4 -> 30, 3 -> 20
181+ return (majorVersion - 1 ) * 10 ;
182+ }
183+
184+ private static final String configuredSSTableFormat ;
82185 private static final CassandraVersion [] implementedVersions ;
83186 private static final String [] supportedVersions ;
84187
85188 static
86189 {
87- sstableFormat = System .getProperty ("cassandra.analytics.bridges.sstable_format" , "big" );
190+ configuredSSTableFormat = System .getProperty ("cassandra.analytics.bridges.sstable_format" , "big" );
88191
89192 // NOTE: These default enum names must stay in sync with cassandraVersionEnumMap in build.gradle.
90193 // FOURONE is intentionally excluded from local-dev defaults to keep iteration fast;
@@ -93,25 +196,25 @@ public String jarBaseName()
93196 String .join ("," , FOURZERO .name (), FIVEZERO .name ()));
94197 implementedVersions = Arrays .stream (providedVersionsOrDefault .split ("," ))
95198 .map (CassandraVersion ::valueOf )
96- .filter (v -> v .sstableFormats .contains (sstableFormat ))
199+ .filter (v -> v .sstableFormats () .contains (configuredSSTableFormat ))
97200 .toArray (CassandraVersion []::new );
98201
99202 // NOTE: These default versions must stay in sync with cassandraFullVersionMap in build.gradle.
100203 String providedSupportedVersionsOrDefault = System .getProperty ("cassandra.analytics.bridges.supported_versions" ,
101204 "cassandra-4.0.17,cassandra-5.0.5" );
102205 supportedVersions = Arrays .stream (providedSupportedVersionsOrDefault .split ("," ))
103206 .filter (version -> CassandraVersion .fromVersion (version )
104- .filter (v -> v .sstableFormats .contains (sstableFormat ))
207+ .filter (v -> v .sstableFormats () .contains (configuredSSTableFormat ))
105208 .isPresent ())
106209 .toArray (String []::new );
107210
108211 Preconditions .checkArgument (implementedVersions .length > 0 && supportedVersions .length > 0 ,
109212 "No versions available" );
110213 }
111214
112- public static String sstableFormat ()
215+ public static String configuredSSTableFormat ()
113216 {
114- return sstableFormat ;
217+ return configuredSSTableFormat ;
115218 }
116219
117220 public static Optional <CassandraVersion > fromVersion (String cassandraVersion )
@@ -122,6 +225,37 @@ public static Optional<CassandraVersion> fromVersion(String cassandraVersion)
122225 .findAny ();
123226 }
124227
228+ /**
229+ * Find the Cassandra version that originally writes SSTables with this version string.
230+ * Returns the native Cassandra version that introduced this SSTable version.
231+ *
232+ * @param sstableVersion full version string including format (e.g., "big-na", "bti-da")
233+ * @return Optional containing the CassandraVersion that natively writes this format,
234+ * or Optional.empty() if:
235+ * <ul>
236+ * <li>The version string is null</li>
237+ * <li>The version string is unrecognized (not in any enum's nativeSStableVersions)</li>
238+ * <li>The version format is invalid or doesn't match expected pattern</li>
239+ * </ul>
240+ */
241+ public static Optional <CassandraVersion > fromSSTableVersion (String sstableVersion )
242+ {
243+ if (sstableVersion == null )
244+ {
245+ return Optional .empty ();
246+ }
247+
248+ for (CassandraVersion version : CassandraVersion .values ())
249+ {
250+ if (version .nativeSStableVersions .contains (sstableVersion ))
251+ {
252+ return Optional .of (version );
253+ }
254+ }
255+
256+ return Optional .empty ();
257+ }
258+
125259 public static CassandraVersion [] implementedVersions ()
126260 {
127261 return implementedVersions ;
0 commit comments