Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MySQL Driver does not populate TIMESTAMPs on record create. #455

Closed
EVILoptimist opened this issue Dec 3, 2014 · 57 comments
Closed

MySQL Driver does not populate TIMESTAMPs on record create. #455

EVILoptimist opened this issue Dec 3, 2014 · 57 comments

Comments

@EVILoptimist
Copy link

When creating a new record in a model with caching enabled (or disabled), if the (MySQL) database table includes a TIMESTAMP column with a default of CURRENT_TIMESTAMP, the record will not include data for the TIMESTAMP column when ->save()d.

CREATE TABLE `widgets` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `ts` timestamp NOT NULL default 'CURRENT_TIMESTAMP',
  `category_id` int(10) unsigned NOT NULL default 0,
  `manufacturer_id` int(10) unsigned NOT NULL default 0,
  `name` varchar(75) NOT NULL default ''
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
$widget = new Widget();
$widget->category_id = $category_id;
$widget->manufacturer_id = $manufacturer_id;
$widget->name = $name;
$widget->save();

echo $widget->ts->setTimezone(new DateTimeZone('America/Denver'));
// Fatal error: Call to a member function setTimezone() on a non-object

My current workaround is to call reload() after save()ing a new record.

$widget = new Widget();
$widget->category_id = $category_id;
$widget->manufacturer_id = $manufacturer_id;
$widget->name = $name;
$widget->save();
$widget->reload();

echo $widget->ts->setTimezone(new DateTimeZone('America/Denver'));
// Thu, 02 Oct 2014 16:43:17 -0600
@EVILoptimist
Copy link
Author

@shmax Thought you might be interested in this as well if you haven't seen something similar already.

@jpfuentes2
Copy link
Owner

@cvanschalkwijk have you seen this issue?

@shmax
Copy link
Contributor

shmax commented Dec 3, 2014

@EVILoptimist Will investigate tonight after work

@shmax
Copy link
Contributor

shmax commented Dec 4, 2014

@EVILoptimist
@jpfuentes2
As far as I can tell it has nothing to do with the individual record caching feature I added, and everything to do with "CURRENT_TIMESTAMP" never being interpreted. However, if you do allow the MysqlAdapater to cast CURRENT_TIMESTAMP to a current DateTime, then that particular time winds up frozen in the cache (with the table metadata, not the record itself), which is a different problem. I can think of two ways to go about fixing this: you can hide a Column's $default member behind an accessor and do the cast upon request, or you can cache the raw table metadata and still build up the Column scaffolding on every request. Here's a PR that demonstrates the latter:

#456

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 4, 2014

If someone can start a PR with a failing test for this specific situation, that would be a great start.

@EVILoptimist
Copy link
Author

@shmax Suppose MySQL was running on a remote server with a different time zone. And CURRENT_TIMESTAMP is expected to take the value of the current time on the server. I am not as familiar with this code as you are, but #456 looks like it could cause some inconsistencies?

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 4, 2014

Maybe an idea to invalidate the cache on save, and create the cache on read?

@shmax
Copy link
Contributor

shmax commented Dec 4, 2014

@koenpunt
Here's your test #457

@shmax
Copy link
Contributor

shmax commented Dec 4, 2014

@koenpunt This doesn't have anything to do with caching of individual records. The bug happens whether you cache or not. The only time caching enters into it is that once you teach your adapter (or column) how to interpret a default value of CURRENT_TIMESTAMP, then you wind up with a collection of cached ActiveRecord\Column records that have the same current time baked into them. The first PR I posted gets around this by caching raw column info, but not the Column records themselves.

@shmax
Copy link
Contributor

shmax commented Dec 4, 2014

I'm working on fixing the unit tests in my original PR. Should have something tonight.

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 4, 2014

I understand, but if the cache is set on fetch of a model, instead of save, then the CURRENT_TIMESTAMP will be filled with the MySQL value

@shmax
Copy link
Contributor

shmax commented Dec 4, 2014

The use case provided by @EVILoptimist doesn't even get as far as fetching anything (well, he does a save, but you can remove that and still see the prob). You can repro the bug without a fetch, and without any cache, so I don't see how we're going to fix anything by discussing fetching/caching. When I mention caching being a problem, I'm talking about the cached table columns. They come into play when you new a model, because the constructor for a model walks over the columns and uses them to come up with suitable default values. So you can start by adding the code to properly convert CURRENT_TIMESTAMP to a current DateTime, but then you have a problem when caching (of table metadata) is turned on, because the current DateTime now stashed in the cached column models never gets refreshed.

So, my first attempt at a fix is very simple; it caches the raw table metadata, but not the Column models themselves, such that they get rebuilt when table() is called for the first time. This will come at the expense of a little speed at run time. I'm going to do a second PR that does cache the column records, but adds a default() accessor method that applies the logic to convert CURRENT_TIME to a DateTime on the fly.

More later.

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 4, 2014

CURRENT_TIMESTAMP is only set on create or update, so if we cache the fetched data, the data in the cache will be correct.

The problem now is that the CURRENT_TIMESTAMP is calculated in MySQL, not PHP. So any kind of date/time resolving in PHP is of no use, as @EVILoptimist already pointed out.

@EVILoptimist EVILoptimist changed the title Model caching does not retrieve TIMESTAMPs on record create. MySQL Driver does not populate TIMESTAMPs on record create. Dec 4, 2014
@koenpunt
Copy link
Collaborator

koenpunt commented Dec 4, 2014

@EVILoptimist can you test my branch https://github.com/koenpunt/php-activerecord/tree/reverse-cache-read-write. Also as PR #458.
Edit; never mind, that doesn't solve your particular problem.

@shmax
Copy link
Contributor

shmax commented Dec 5, 2014

@EVILoptimist @koenpunt
I'm still having trouble getting the unit tests run by github to pass on my PR; the new test related to this particular issue seems to pass no problem, but I'm getting all these weird errors related to PgsqlAdapter that don't seem to have anything to do with the changes I made, and in fact I can't even find the failing tests in the codebase.

Anyway, can you explain a little more about what you mean by time being calculated in PHP vs MySQL? I still don't see the problem; by calculating the time in PHP and explicitly passing it in when doing our first write, we're not even giving MySQL a chance to invoke CURRENT_TIMESTAMP. And when we do the write, we use a format that has the timezone in it, so there should be no ambiguity, right?

Not saying you're wrong--you're probably right--just that I need some help understanding.

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 5, 2014

My explanation of the problem of @EVILoptimist (correct me if I'm wrong):

$widget = new Widget();
$widget->category_id = $category_id;
$widget->manufacturer_id = $manufacturer_id;
$widget->name = $name;

$widget->save();
$widget->ts #=> null;

At this point, the database has filled the ts column with the CURRENT_TIMESTAMP, but as it has been filled on the database side, the model does not know about it.

The only way to get the database-filled CURRENT_TIMESTAMP, is by fetching the data from the database.

# calling reload on the model
$widget->reload();
# or by initiating a new find 
$widget = Widget::find($widget->id);

The fact that the incorrect values were cached, was because it cached the created model, not the fetched one. That issue will be resolved by #458, where the cache will be created on first fetch, including the database-filled CURRENT_TIMESTAMP. But that's obviously a different issue.

My final note, a quote of @bladenkerst from years ago (#2):

Ideally one would not use CURRENT_TIMESTAMP on a database while using the ActiveRecord pattern

And I agree, just like the updated_at column gets populated from PHP (lib/Table.php#L100), you should probably do the same for your ts column:

class Widget extends ActiveRecord\Model {
  public function before_save(){
    $this->ts = date('Y-m-d H:i:s');
  }
}

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 5, 2014

@jpfuentes2 @cvanschalkwijk @al-the-x @Rican7; I like your word on this, but I don't think that dynamic default columns is something we're going to implement, right?

@EVILoptimist
Copy link
Author

@shmax The problem with calculating time on PHP vs MySQL only manifests when you have multiple servers with dedicated roles (and possibly with different timezone settings). I am using my dedicated MySQL server in this instance for a common point of reference for time. The CURRENT_TIMESTAMP is perfect for making sure that records are not being stamped with a possible out-of-sync (or out-of-timezone) time. This is especially useful for clients that might be connecting via WAN.

@koenpunt I agree that the addition of a before_save() function is a simple solution for this issue and might even be best practice. It, however, does not meet the needs of my implementation.

You have given me a bit of inspiration, however:

class Widget extends ActiveRecord\Model {
  public function after_save(){
    $this->reload();
  }
}

This seems to accomplish the same task without the need of appending the Model::reload() call to my creation functions. I could probably also further optimize by writing a conditional based on my TIMESTAMP columns.

@shmax
Copy link
Contributor

shmax commented Dec 5, 2014

"At this point, the database has filled the ts column with the CURRENT_TIMESTAMP, but as it has been filled on the database side, the model does not know about it."

@koenpunt But that's not correct; you have the order of operations backward. When you "new" a model, the first thing that happens is the constructor fires. Now, look at line 269 of Model.php. See that foreach, there? It's walking over each column defined by the table schema, and using its knowledge of the default property to pre-initialize the attributes.

In our case, "CURRENT_TIMESTAMP" is the default, so we pre-emptively prime our ts with the current time (in my PR). When we do our first save, that time is written directly into the first INSERT statement, so MYSQL never gets a chance to come up with a default date itself. This is true of any default value; try it with a text field, for example; same behavior.

Because MYSQL never gets a chance to fire its own CURRENT_TIMESTAMP, I don't see how the timezone the MySQL server is in makes any difference.

There may be some other use case that presents a problem, but the one we've been working with does not suffer from the issue you describe. I've been testing with my changes, and so far it not only works perfectly, but passes the unit test.

Doing "reload" is not a fix; it leaves the unit test I created unresolved.

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 5, 2014

It's walking over each column defined by the table schema, and using its knowledge of the default property to pre-initialize the attributes.

Yes, but the behavior of CURRENT_TIMESTAMP is very much depending on the MySQL server. The only way of getting the actual CURRENT_TIMESTAMP is by querying: SELECT CURRENT_TIMESTAMP

Because MYSQL never gets a chance to fire its own CURRENT_TIMESTAMP, I don't see how the timezone the MySQL server is in makes any difference.

What is the use of CURRENT_TIMESTAMP on the column if it's not going to be used? If you want the timestamp to be populated from PHP you shouldn't add DEFAULT CURRENT_TIMESTAMP to the column definition in the first place.
And besides that, the CURRENT_TIMESTAMP should be in the timezone of the MySQL server, not in the of the application server (in cases where the MySQL and application server are not the same).

Doing "reload" is not a fix; it leaves the unit test I created unresolved.

True, because its a undesired use case.

So to sum it up, there is no use in implementing a very dynamic default column value.

@shmax
Copy link
Contributor

shmax commented Dec 5, 2014

Pretend for the moment that we're not talking about dates. Let's say we're talking about a text field, and we set it up with a default value of "puppies". In ActiveRecord, we come pre-armed with information about the table schema. We know about the "puppies" default before we've done our first write, or fetch, or reload, or what have you. When we instantiate a new record using "new", we use that advance knowledge to load our in-memory object with the default values. So we can do this:

$myRecord = new FavoriteAnimal();
echo $myRecord->animal; // "puppies

This is before we've done even a single write or read from the database. Now, when we save our record for the very first time...

$myRecord->save();

We pass the attributes into an insert statement, and those attributes have "puppies" in them. Do you follow that? Even though we defined "puppies" as a default value in our schema, MYSQL was never impelled to rely on it, because we wind up passing it in expliclty from the outside.

No one seems to be alarmed by that behavior; it works very well and is at the heart of the active record pattern.

So when you say "What is the use of CURRENT_TIMESTAMP on the column if it's not going to be used?", I can turn it around on you and say "What is the use of defining a default value on a text field if it's not going to be used?" My answer is well, that's how the active record pattern works.

Now, let's consider a timestamp value with some explicit default, such as "12/4/2014 6:00:00 AM". Work through the same series of steps, again; we grab the default from the schema, new our object, save it, and create an insert statement with that value in it. Still no complaints, right?

In both these examples, remember that we never get as far as needing MYSQL to rely on those default values when doing its first insert; we pass them in explicitly.

Now, take a deep breath and consider CURRENT_TIMESTAMP. Walk through the same steps again. The only difference here is that we're interpreting the CURRENT_TIMESTAMP in code. Otherwise the pattern is exactly the same! We initialize our default date in our constructor, we do our first insert with the value filled-in, and MySQL never gets a chance to invoke its default value (because we pass it in).

As for timezone issues, I still don't see the issue. Active record does its date writes with a format that has the timezone in it (which I find a little confusing; I would expect an ISO 8601 format to be easier to work with), so there's not much room to get things wrong (and if there is, it's fixable).

Try it for yourself. Download my branch, fire up a debugger, and step through the following code:

$widget  = Widget();
print_r($widget->some_date);

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 5, 2014

I can be short, I know exactly what you mean, but CURRENT_TIMESTAMP is a dynamic, not a static default like your 'puppies' example.

@shmax
Copy link
Contributor

shmax commented Dec 5, 2014

So? What's the difference? Where's the bug? Can you show me a test case that illustrates it?

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 5, 2014

There is no bug. There is simply no support for dynamic default values. And if it's for me to decide it stays that way (Rails' ActiveRecord doesn't support it either). @jpfuentes2 @cvanschalkwijk what do you guys think?

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 5, 2014

If you want your column prefilled you can always add a getter to the corresponding model:

class Widget extends ActiveRecord\Model {
  public function get_ts(){
    return $this->read_attribute('ts') ?: new DateTime();
  }
}

@shmax
Copy link
Contributor

shmax commented Dec 6, 2014

You would rather leave a timebomb of a bug in the code when a fix is not only possible, but in your possession? Do you want people to keep opening issues when they encounter this bug?

To be clear, you are essentially requiring people to do three times as many database operations as they would otherwise need to in anything but the most vanilla of scenarios. Yes, it works, but it's creating more load on the server, and you're now requiring special handling that is not intuitive unless you have spent time analyzing the codebase:

class WidgetHandler(){
  function getWidget($widgetId){
     try{
         $widget = Widget::find($widgetId);
     }
    catch(Exception $e){
         $widget = new Widget();
         $widget->save(); // EXTRA DATABASE WRITE
         $widget->reload(); // EXTRA DATABASE READ
    }

   return $widget;
 }

 function updateWidget(Widget $widget){
     $widget->is_logged = true;
       $this->logDateCreated($widget);
 } 

 function logDateCreated(Widget $widget){
     writeLog("widget was created at ".$widget->ts->format("h:i:s"));
 }
}

$wf = new WidgetHandler();
$widget = $wf->getWidget($_GET['widgetId']);
$wf->updateWidget($widget);
$widget->save();

I offer two compromises:

  1. Make it configurable. If a consumer of the library agrees with your philosophical objection, they can leave the feature disabled. I'll even make it disabled by default. If they disagree, they can enable it, and never have to double save again.
  2. No one has said a peep about my actual fix yet, but if anyone objects to the way I handle table metadata loading, I have an alternate idea that would localize the concept completely in the MySql adapter (or possibly a MySqlColumn class), and the only people any of this would matter to in the slightest are MySql users.

As for the timezone issue, I've asked for a specific use case demonstrating the problem, but have not yet received one. I'm willing to write some some test cases around the concept if it would help us understand the issue.

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 6, 2014

@shmax There is a proposed solution, using the before_save, and as @EVILoptimist did point out, that doesn't solve his problem (Although the suggestion brought him to a workaround). But, neither does yours. So whose problem are we fixing here?

Nb. we appreciate your contributions, but the way you propose your compromises as an ultimatum is not how open source works. Sometimes, things that look very important to you, are not that important to the community.

@shmax
Copy link
Contributor

shmax commented Dec 6, 2014

@koenpunt My fix does solve his problem. I even provided a test that illustrates it. It fails without my fix, and passes with it. If you are philosophically opposed to my method (coming up with the current time in PHP), then you should also be philosophically opposed to the before_save idea; they are fundamentally the same. The only difference is that mine is automatic and doesn't require the user to be aware of the issue or do anything special. I have not issued any ultimatums. I have asked that you not rush to close the issue until it is better understood by us all. You obviously didn't understand it very well, as evidenced by your failed fix, and your failed workaround. My understanding is not complete, either, as various folks have hinted that there may be a timezone issue. Let's work together to figure it out.

@cvanschalkwijk
Copy link
Collaborator

@shmax I've re-opened your pull requests and we will review your code there.

@shmax
Copy link
Contributor

shmax commented Dec 6, 2014

@cvanschalkwijk Thank you. I'm working hard to try to get all the tests to pass, but I'm having trouble with SQLite ("Database is locked") in my local environment.

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 6, 2014

I will quote @EVILoptimist (#455 (comment)):

@shmax The problem with calculating time on PHP vs MySQL only manifests when you have multiple servers with dedicated roles (and possibly with different timezone settings). I am using my dedicated MySQL server in this instance for a common point of reference for time. The CURRENT_TIMESTAMP is perfect for making sure that records are not being stamped with a possible out-of-sync (or out-of-timezone) time.

So he's saying he uses his db as date reference, not the date of php. but maybe @EVILoptimist himself can clear the sky for us?

And you really should stop making accusations and suggesting I'm dumb or not understanding you (I understand you perfectly, I'm just not agreeing) if your going to talk about respect and all

@EVILoptimist
Copy link
Author

Yes. In my implementation. I cannot guarantee that timestamps that are
received by the database will be as accurate as I would like to have
guaranteed. So, I have permissions set such that the timestamps cannot be
written by users. This guarantees a common point of reference for
timestamps on the inserted records.
On Dec 6, 2014 1:19 PM, "Koen Punt" notifications@github.com wrote:

I will quote @EVILoptimist https://github.com/EVILoptimist (#455
(comment)
#455 (comment)
):

@shmax https://github.com/shmax The problem with calculating time on
PHP vs MySQL only manifests when you have multiple servers with dedicated
roles (and possibly with different timezone settings). I am using my
dedicated MySQL server in this instance for a common point of reference for
time. The CURRENT_TIMESTAMP is perfect for making sure that records are not
being stamped with a possible out-of-sync (or out-of-timezone) time.

So he's saying he uses his db as date reference, not the date of php. but
maybe @EVILoptimist https://github.com/EVILoptimist himself can clear
the sky for us?

And you really should stop making accusations and suggesting I'm dumb or
not understanding you (I understand you perfectly, I'm just not agreeing)
if your going to talk about respect and all
#455 (comment)


Reply to this email directly or view it on GitHub
#455 (comment)
.

@shmax
Copy link
Contributor

shmax commented Dec 6, 2014

@koenpunt You're not dumb, just rash. You ignored my PR, and wrote your own "easy fix", which didn't even remotely work. Then you provided a workaround which was patently wrong. When pressed, you decided that because Ruby doesn't deal with it, we shouldn't either, and just slammed the whole thing closed in my face, apparently more out of frustration than a desire for resolution. That isn't exactly respectful, either.

My fix may very well be wrong, too, but it would be nice if we could establish that through some test cases, and not just angry hand-waving.

@EVILoptimist Can you explain with a code sample how the dates can diverge or be off? If you are using ActiveRecord to create your rows, and (with my proposed fix) the dates are always being taken from the server and consistent among themselves, where is the issue?

I'm still having trouble understanding how a server's timezone makes a difference. If I give you, say, a date in GMT format, the timezone doesn't matter. The date is absolute, and the timezone only really comes into play when displaying it to the user, right?

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 6, 2014

I'm not the one who's angry here.. You keep ranting about my behavior (what does that say about your behavior?). You keep saying my solutions are not working (they do, but they don't solve this particular issue). And last, what makes you think I'm frustrated? I just closed this issue with possible workarounds, and moved on with my live. You try to make an issue of a problem that actually only exist of the assumption you made of @EVILoptimist's problem.
Anyways, discussing manners is of no use. Ranting is even less.

@cvanschalkwijk
Copy link
Collaborator

@shmax @koenpunt please keep it constructive and on topic. I took a look at the rails AR pattern and reviewing a few other ORM libraries to see how other people have handled this same issue. This just popped up a few days ago so it will take some time to figure out what the best solution might be, if any.

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 6, 2014

👍

@shmax
Copy link
Contributor

shmax commented Dec 6, 2014

My fix addresses the original snippet he posted. Before my fix, his snippet fails. With my fix, it doesn't fail. This is not true of any of your solutions. And that's fine. You are allowed to make mistakes, as are we all.

We've since learned that @EVILoptimist would prefer that any and all dates be generated solely by his MYSQL server, and that's fine. There is no point in arguing whether he has a point or not; he wants to do it the way he wants to do it. That doesn't mean it's time to throw up our hands and move on.

@shmax
Copy link
Contributor

shmax commented Dec 6, 2014

@cvanschalkwijk sgtm

@EVILoptimist
Copy link
Author

@shmax I don't believe (and I could be wrong as I can't verify ATM) mysql
stores tune zones in the timestamp record. But, my concern had less to do
with a time zone and more to do with a server that I don't have control
over and might have (maliciously?) manipulated their time settings to
misrepresent their data. As the data is stored on my server (which I have
control of), but some of the data being stored comes from this party
servers making a remote connection.

@koenpunt https://github.com/koenpunt You're not dumb, just rash. You
ignored my PR, and wrote your own "easy fix", which didn't even remotely
work. Then you provided a workaround which was patently wrong. When
pressed, you decided that because Ruby doesn't deal with it, we shouldn't
either, and just slammed the whole thing closed in my face, apparently more
out of frustration than a desire for resolution. That isn't exactly
respectful, either.

My fix may very well be wrong, too, but it would be nice if we could
establish that through some test cases, and not just angry hand-waving.

@EVILoptimist https://github.com/EVILoptimist Can you explain with a code
sample how the dates can diverge or be off? If you are using ActiveRecord
to create your rows, and (with my proposed fix) the dates are always being
taken from the server and consistent among themselves, where is the issue?

I'm still having trouble understanding how a server's timezone makes a
difference. If I give you, say, a date in GMT format, the timezone doesn't
matter. The date is absolute, and the timezone only really comes into play
when displaying it to the user, right?


Reply to this email directly or view it on GitHub
#455 (comment)
.

@shmax
Copy link
Contributor

shmax commented Dec 6, 2014

@shmax my concern had less to do
with a time zone and more to do with a server that I don't have control
over and might have (maliciously?) manipulated their time settings to
misrepresent their data. As the data is stored on my server (which I have
control of), but some of the data being stored comes from this party
servers making a remote connection.

Ahh, now we're getting somewhere. Okay, so that's valid for your situation, and by all means my fix is not appropriate for you.

But the issue still stands for other people without the same concerns, such as myself. I use ActiveRecord on a production site, and I use MySQL, and I would very much like to be able to use CURRENT_TIMESTAMP without having to rely on extra reads and writes. It seems like something that could be easily handled with some configuration settings. There could be a global config, and one at the model level that can optionally override the global one.

@shmax
Copy link
Contributor

shmax commented Dec 6, 2014

@EVILoptimist ActiveRecord aside, how do you prevent a malicious user from setting crazy time values directly via MySQL queries?

@EVILoptimist
Copy link
Author

I agree. I understand the issues with the driver, but since I'm able to
access the pk of an object after save()ing, it just made sense to me to be
able to access other server-assigned fields as well. Hence this report.
On Dec 6, 2014 3:21 PM, "Max Loeb" notifications@github.com wrote:

@shmax https://github.com/shmax my concern had less to do
with a time zone and more to do with a server that I don't have control
over and might have (maliciously?) manipulated their time settings to
misrepresent their data. As the data is stored on my server (which I have
control of), but some of the data being stored comes from this party
servers making a remote connection.

Ahh, now we're getting somewhere. Okay, so that's valid for your
situation, and by all means my fix is not appropriate for you.

But the issue still stands for other people without the same concerns,
such as myself. I use ActiveRecord on a production site, and I use MySQL,
and I would very much like to be able to use CURRENT_TIMESTAMP without
having to rely on extra reads and writes. It seems like something that
could be easily handled with some configuration settings. There could be a
global config, and one at the model level that can optionally override the
global one.


Reply to this email directly or view it on GitHub
#455 (comment)
.

@koenpunt
Copy link
Collaborator

koenpunt commented Dec 7, 2014

I agree. I understand the issues with the driver, but since I'm able to
access the pk of an object after save()ing, it just made sense to me to be
able to access other server-assigned fields as well. Hence this report.

This made me realize CURRENT_TIMESTAMP is just like an auto increment, there is no reliable way of knowing what it is, until the record is actually created.

$widget = new Widget();
$widget->id; #=> null

And there is a efficient way to retrieve the insert id (PDO::lastInsertId) without doing an extra query for it, so its included after save:

$widget = new Widget();
$widget->save();
$widget->id; #=> 1

But for CURRENT_TIMESTAMP, there's not (or at least not that I know of).

@shmax
Copy link
Contributor

shmax commented Dec 7, 2014

That's actually a very good insight.

@shmax
Copy link
Contributor

shmax commented Dec 7, 2014

You could always do this once per session:

SELECT NOW()

That gets you the current time from the database. Store it in a static somewhere so you only have to do it once, and use it in place of time() or $_SERVER['REQUEST_TIME'];

@shmax
Copy link
Contributor

shmax commented Dec 7, 2014

@EVILoptimist

Do you like this any better?

#465

With this method, you supply your own current time to the ActiveRecord config (it defaults to letting PHP come up with it), and that gets used when interpreting CURRENT_TIMESTAMP:

ActiveRecord\Config::initialize(function($cfg){
        $row = $database->execute("select now() as current_ts");
        $cfg->set_current_time(new ActiveRecord\DateTime($row['current_ts']));
});

@jpfuentes2
Copy link
Owner

I'm keeping my eyes on this now. Thanks to everyone for the descriptions, insights, and staying on topic.

@cvanschalkwijk
Copy link
Collaborator

After discussing it with @kla and @jpfuentes2 we are going to leave the current implementation as is. There is no reliable way to predict storage engine timestamps and the reload() method already exists in cases where the timestamp is needed after a save() and was not explicitly set by the developer prior to saving.

Other ORMs out there have already made a similar decision.

@shmax
Copy link
Contributor

shmax commented Dec 8, 2014

Yeah, I thought about it some more, and nobody made this argument, but this illustrates the problem with my solution:

$w1 = new Widget();
sleep(1);
$w2 = new Widget();

assert($w1->ts != $w2->ts); // fail

@shmax
Copy link
Contributor

shmax commented Dec 8, 2014

True enough.

@shmax
Copy link
Contributor

shmax commented Dec 8, 2014

Hmm, I guess you could get pretty close by querying the database for the current time, then adding a delta whenever CURRENT_TIME is requested. But I'm going to move on.

@EVILoptimist
Copy link
Author

@shmax OK. I just commented on #465. And, I'm pretty excited about that kind of a solution if it's feasible. But I guess we're beating a dead horse at this point?

@cvanschalkwijk
And, yes, using the reload() is a solution that works as needed.
No disrespect, but "Other ORMs out there have already made a similar decision." is exactly the reason I would try to fix it, but that's my style :)

@shmax
Copy link
Contributor

shmax commented Dec 8, 2014

@EVILoptimist I don't think we're going to win any arguments here, but if you're interested we can see if we can get it polished up enough to merge into my fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants