Is it possible to automatically append database name to a table name in laravel?
The issue is that I have to join data from multiple databases in single queries and sometime I am having to manually replace template names, which is a lot of hassle.
The only solution that I found is that I can append database name to the table name within a model, i.e.
class User extends Model
{
protected $table = 'database_name.table_name';
}
But with above we are losing support for table prefixes.
Example when database name is not applied:
$userQuery = User::where('id', 1)
->with('settings')
->select('some data');
DB::connection('x')
->table('table-on-different-connection')
->insertUsing(['some columns'], $userQuery);
$userQuery is on a different connection and database_name was not applied to the tables within that part of the query. Hence why insertUsing is trying to perform joins on connection x.
Laravel is not appending database name when generating SQL statements. To resolve that, you need to create your own MySQL wrapper and append the database name to the table name that way.
This is where the issue takes place:
vendor\laravel\framework\src\Illuminate\Database\Query\Grammar.php
public function wrapTable($table)
{
if (! $this->isExpression($table)) {
return $this->wrap($this->tablePrefix.$table, true);
}
return $this->getValue($table);
}
You need to override wrapTable method and append database name to the table that way.
i.e.
public function wrapTable($table)
{
$databaseName = $this->wrap('my_database'); // dynamically defined name here
if (! $this->isExpression($table)) {
$tableName = $this->wrap($this->tablePrefix.$table, true);
return "{$databaseName}.{$tableName}";
}
return $this->getValue("{$databaseName}.{$table}");
}
How you go about extending Grammar and override this method depends on your application and your needs. This can be done globally (i.e. via AppProvider) or for an individual query.
Related
Using Entity Framework 6 and MySQL, I am trying to archive data from a 'production' database table to an 'archive' database. I have created two DBContexts one for each database. Each database has the same schema.
I can move an entire table of data from the production database to the archive database using the following code:
using (MyDBContext archiveContext =
MyDBContext.CreateEntitiesForSpecificDatabaseName("archive_db"))
using (MyDBContext prodContext =
MyDBContext.CreateEntitiesForSpecificDatabaseName("prod_db"))
{
if(prodContext.myTable.Any())
{
archiveContext.myTable.AddRange(prodContext.myTable.AsNoTracking());
archiveContext.SaveChanges();
}
}
However I don't want to archive the whole table, I only wish to archive data older than a certain date, so I tried the following:
using (MyDBContext archiveContext =
MyDBContext.CreateEntitiesForSpecificDatabaseName("archive_db"))
using (MyDBContext prodContext =
MyDBContext.CreateEntitiesForSpecificDatabaseName("prod_db"))
{
IQueryable<myTable> dataToArchive =
from mt in prodContext.myTable
where mt.date < DateTimeSixMonths
select mt;
archiveContext.myTable.AddRange(dataToArchive);
archiveContext.SaveChanges();
}
but I cannot get around the exception I get when I run this:
System.InvalidOperationException: 'An entity object cannot be
referenced by multiple instances of IEntityChangeTracker.'
It occurs on this line:
archiveContext.myTable.AddRange(dataToArchive);
Is it possible to somehow remove the tracking from the 'dataToArchive'
Have you tried disposing the first DataContext after retrieving data? Something like this:
List<myTable> dataToArchive;
using (MyDBContext prodContext =
MyDBContext.CreateEntitiesForSpecificDatabaseName("prod_db"))
{
dataToArchive = (from mt in prodContext.myTable
where mt.date < DateTimeSixMonths
select mt).ToList();
}
using (MyDBContext archiveContext =
MyDBContext.CreateEntitiesForSpecificDatabaseName("archive_db"))
{
archiveContext.myTable.AddRange(dataToArchive);
archiveContext.SaveChanges();
}
Using EF to manage archiving data isn't ideal, something like that would be better served at the database level using insert-select + delete for low to moderate data volumes or detachable partitions (I.e. 3-6 mo. partition sizes) that can be moved between databases.
To do this with EF (only recommended for small and non-complex domain models) you should be able to accomplish this by disabling the proxy generation in your read context, load the data AsNoTracking, then add it to the new context DbSet. This example does not handle associated entities, or do the delete from the prod DbSet.
using (MyDBContext prodContext =
MyDBContext.CreateEntitiesForSpecificDatabaseName("prod_db"))
{
prodContext.Configuration.ProxyCreationEnabled = false;
dataToArchive = prodContext.myTable.AsNoTracking()
.Where(mt => mt.Date < DateTimeSixMonths);
using (MyDBContext archiveContext =
MyDBContext.CreateEntitiesForSpecificDatabaseName("archive_db"))
{
archiveContext.myTable.AddRange(dataToArchive);
archiveContext.SaveChanges();
}
}
What do you use to get the checksum of a table in Laravel? Is there something already abstracted for this or you have to use raw commands?
You have to use raw commands, but it is pretty easy, just add this method to your model:
public static function checksum()
{
$tableName = with(new static)->getTable();
$query = sprintf('CHECKSUM TABLE %s', $tableName);
return \DB::select(\DB::raw($query))[0]->Checksum;
}
You can now call this method statically to get the checksum.
I am trying to do an easy search on a table that can be on any kind of database. The following query is working an the most databases, but I cannot find a solution which works on mysql.
The tables in my database are generated by the active objects framework, so I cannot change the names or config of those instances.
Here is the query that works fine on all databases but MySQL:
select * from "AO_69D057_FILTER" where "SHARED" = true AND "CONTAINS_PROJECT" = true AND UPPER("FILTER_NAME") like UPPER('%pr%').
MySql is not able to use the table name in double quotes for some reason. If I use the unquoted table name it works on MySQL but not on Postgres. Postgres is converting the table name to lowercase because it is unquoted. AO is generating the table names in upper case.
I also tried to use an alias, but that can not work because of the evaluation hierarchy of the statement.
Any suggestions how to get rid of the table name problem?
By default double quotes are used to columns.
You can change it:
SET SQL_MODE=ANSI_QUOTES;
Here is the documentation about it:
http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html
I had the same problem. I select the query according to the exception I get. In the first call of the db search, I try without quotes if it fails then I try with quotes. Then I set useQueryWithQuotes variable accordingly so that in future calls I do not need to check the exception. Below is the code snipped I am using.
private Boolean useQueryWithQuotes=null;
private final String queryWithQuotes = "\"OWNER\"=? or \"PRIVATE\"=?";
private final String queryWithoutQuotes = "OWNER=? or PRIVATE=?";
public Response getReports() {
List<ReportEntity> reports = null;
if(useQueryWithQuotes==null){
synchronized(this){
try {
reports = new ArrayList<ReportEntity>( Arrays.asList(ao.find(ReportEntity.class, Query.select().where(queryWithoutQuotes, getUserKey(), false))) );
useQueryWithQuotes = false;
} catch (net.java.ao.ActiveObjectsException e) {
log("exception:" + e);
log("trying query with quotes");
reports = new ArrayList<ReportEntity>( Arrays.asList(ao.find(ReportEntity.class, queryWithQuotes, getUserKey(), false)));
useQueryWithQuotes = true;
}
}
}else{
String query = useQueryWithQuotes ? queryWithQuotes : queryWithoutQuotes;
reports = new ArrayList<ReportEntity>( Arrays.asList(ao.find(ReportEntity.class, query, getUserKey(), false)));
}
...
}
I have one database in which all table names like below
configuration_dst,
developer_dst,
application_dst
Now I want to manage my query in which want to add database prefix after table name instead of before.
For example :
{TABLE NAME}{PREFIX}
is it possible to manage using CI 3.0 ?
I have tried like below in Application/configuration/database.php
$db['default']['dbprefix']="_dst";
$db['default']['swap_pre']="{POST}";
Current Query :
$this->db->get('templates');
Current Table Name :
tablename : _dsttemplates
Expected Table Name :
tablename : templates_dst
I need prefix after table Name not before but didn't get any solution.
The only option is to change in driver files. You can do this using the following steps:
Go to /system/database/DB_query_builder.php
Search public function dbprefix
Replace
return $this->dbprefix.$table;
with
return $table.$this->dbprefix;
If it also interferes in any other places in your project then create a new function with replaced code like:
public function dbsuffix($table = '')
{
if ($table === '')
{
$this->display_error('db_table_name_required');
}
return $table.$this->dbprefix;
}
Working with Database prefixes manually
if you use
$this->db->set_dbprefix('newprefix');
$this->db->dbprefix('tablename'); // outputs newprefix_tablename
its always gives the table name as
newprefix_tablename
Because this is Codeigniter pastern.
Codeigniter Prefix
Follow up to this question. I have the following code:
string[] names = new[] { "Bob", "bob", "BoB" };
using (MyDataContext dataContext = new MyDataContext())
{
foreach (var name in names)
{
string s = name;
if (dataContext.Users.SingleOrDefault(u => u.Name.ToUpper() == s.ToUpper()) == null)
dataContext.Users.InsertOnSubmit(new User { Name = name });
}
dataContext.SubmitChanges();
}
...and it inserts all three names ("Bob", "bob" and "BoB"). If this was Linq-to-Objects, it wouldn't.
Can I make it look at the pending changes as well as what's already in the table?
I don't think that would be possible in general. Imagine you made a query like this:
dataContext.Users.InsertOnSubmit(new User { GroupId = 1 });
var groups = dataContext.Groups.Where(grp => grp.Users.Any());
The database knows nothing about the new user (yet) because the insert wasn't commited yet, so the generated SQL query might not return the Group with Id = 1. The only way the DataContext could take into account the not-yet-submitted insert in cases like this would be to get the whole Groups-Table (and possibly more tables, if they are affected by the query) and perform the query on the client, which is of course undesirable. I guess the L2S designers decided that it would be counterintuitive if some queries took not-yet-committed inserts into account while others wouldn't, so they chose to never take them into account.
Why don't you use something like
foreach (var name in names.Distinct(StringComparer.InvariantCultureIgnoreCase))
to filter out duplicate names before hitting the database?
Why dont you try something like this
foreach (var name in names)
{
string s = name;
if (dataContext.Users.SingleOrDefault(u => u.Name.ToUpper() == s.ToUpper()) == null)
{
dataContext.Users.InsertOnSubmit(new User { Name = name });
break;
}
}
I am sorry, I don't understand LINQ to SQL as much.
But, when I look at the code, it seems you are telling it to insert all the records at once (similar to a transaction) using SubmitChanges and you are trying to check the existence of it from the DB, when the records are not inserted at all.
EDIT: Try putting the SubmitChanges inside the loop and see that the code will run as per your expectation.
You can query the appropriate ChangeSet collection, such as
if(
dataContext.Users.
Union(dataContext.GetChangeSet().Inserts).
Except(dataContext.GetChangeSet().Deletes).
SingleOrDefault(u => u.Name.ToUpper() == s.ToUpper()) == null)
This will create a union of the values in the Users table and the pending Inserts, and will exclude pending deletes.
Of course, you might want to create a changeSet variable to prevent multiple calls to the GetChangeSet function, and you may need to appropriately cast the object in the collection to the appropriate type. In the Inserts and Deletes collections, you may want to filter it with something like
...GetChangeSet().Inserts.Where(o => o.GetType() == typeof(User)).OfType<User>()...