In a C++ application that can use just about any relational database, what would be the best way of generating queries that can be easily extended to allow for a database engine’s eccentricities?
In other words, the code may need to retrieve data in a way that is not consistent among the various database engines. What’s the best way to design the code on the client side to generate queries in a way that will make supporting a new database engine a relatively painless affair.
For example, if I have (MFC)code that looks like this:
CString query = 'SELECT id FROM table' results = dbConnection->Query(query);
and we decide to support some database that uses, um, ‘AVEC’ instead of ‘FROM’. Now whenever the user uses that database engine, this query will fail.
Options so far:
- Worst option: have the code making the query check the database type.
- Better option: Create query request method on the db connection object that takes a unique query ‘code’ and returns the appropriate query based on the database engine in use.
- Betterer option: Create a query builder class that allows the caller to construct queries without using any SQL directly. Once the query is completed, caller can invoke a ‘Generate’ method which returns a query string approrpriate for the active database engine
- Best option: ??
Note: The database engine itself is abstracted away through some thin layers of our own creation. It’s the queries themselves are the only remaining problem.
Solution:
I’ve decided to go with the ‘better’ option (query ‘selector’) for two reasons.
- Debugging: As mentioned below, debugging is going to be slightly easier with the selector approach since the queries are pre-built and listed out in a readable form in code.
- Flexibility: It occurred to me that there are some databases which might have vastly better and completely different ways of solving a particular query. For example, with Access I perform a complicated query on multiple tables each time because I have to, but on Sql Server I’d like to setup a view. Selecting from the view and from several tables are completely different queries (i think) and this query selector would handle it easily.
I would think that what you would want to do, if you needed the ability to support multiple databases, would be to create a data provider interface (or abstract class) and associated concrete implementations. The data provider would need to support your standard query operators and other common, supported functionality required support your query operations (have a look at IEnumerable extension methods in .NET 3.5). Each concrete provider would then translate these into specific queries based on the target database engine.
Essentially, what you do is create a database abstraction layer and have your code interact with it. If you can find one of these for C++, it would probably be worth buying instead of writing. You may also want to look for Inversion of Control (IoC) containers for C++ that would basically do this and more. I know of several for Java and C#, but I’m not familiar with any for C++.