I'm trying to display data from a non-Sugar table in a custom listview in SugarCRM. Currently I'm running an SQL query in a custom view.list.php file, but this is displaying the data below the list rather than replacing the default query in the listview.
How can I replace the default query in a listview with custom SQL?
You don't have to go through all of that.
When you create a custom module in ModuleBuilder. Deploy the package
when edit the vardefs.php and Module_sugar.php and change the table_name to point to the new table. Then you don't actually have to write any special code and custom fields will work and complete the join for you.
class CustomModule_sugar extends SugarBean {
var $table_name = 'external_table';
I've managed to solve this by overriding the create_new_list_query() method in the module base class:
class CustomModule extends CustomModule_sugar {
function CustomModule(){
parent::CustomModule_sugar();
}
// this is the method which constructs the default SQL query
function create_new_list_query($order_by, $where, $filter, $params, $show_deleted, $join_type, $return_array, $parentbean, $singleSelect){
// call the parent method to populate all params - will cause errors/problems elsewhere otherwise
$ret_array = parent::create_new_list_query($order_by, $where,$filter,$params, $show_deleted,$join_type, $return_array,$parentbean, $singleSelect);
// override module sql with custom query
// alias external field names so they match the fields set up in Sugar
$ret_array['select'] = 'SELECT primary_id as id, date_added as date_entered, field_name as name, external_notes as notes';
$ret_array['from'] = ' FROM external_table';
// update these with appropriate SQL
$ret_array['where'] = '';
$ret_array['order_by'] = '';
return $ret_array;
}
}
This method creates an SQL statement which is used in /includes/ListView/ListViewData.php. I've aliased the field names selected from the external table to match the names of the fields set up in Sugar (easier than creating or renaming every single field).
Related
I added a couple of virtual columns to my database tables using Laravels virtualAs column modifier:
$table->decimal('grand_total')->virtualAs( '(total_value + (total_value*tax_rate))');
Basically it keeps a mysql virtual column that automatically calculates the grand total based on the total and tax rate stored in another column.
However, Laravel does not seem to play nice with virtual columns at all. When saving a record, it attempts to INSERT or UPDATE the virtual column, which is obviously not allowed in mySQL. I could not find a way to configure in the Eloquent model which fields are actually written to the database on an update or insert.
I've tried adding the field to the models $hidden, and $appends but nothing seems to work.
Looking at the Laravel Source code for an insert (https://github.com/laravel/framework/blob/5.6/src/Illuminate/Database/Eloquent/Model.php#L733), it seems to just insert whatever attributes are in $this->attributes. When the record is read from the database the grand_total field is read from the table and set as an attribute and then it is tried to be written again once the record is saved.
Is there any way to get this Laravel to stop trying to save columns that are virtual?
Here's a quick trait I wrote to solve your problem that will filter fields residing in the $virtualFields property before saving. It requires a select (refresh) after the save to get the new value for the virtual field. If you don't need to query this virtual field, I'd highly recommend you look into a mutator instead.
trait HasVirtualFields
{
public function save(array $options = [])
{
if (isset($this->virtualFields)) {
$this->attributes = array_diff_key($this->attributes, array_flip($this->virtualFields));
}
$return = parent::save($options);
$this->refresh(); // Refresh the model for the new virtual column values
return $return;
}
}
class YourModel
{
use HasVirtualFields;
protected $virtualFields = ['grand_total'];
}
I am using PHP Yii framework's Active Records to model a relation between two tables. The join involves a column and a literal, and could match 2+ rows but must be limited to only ever return 1 row.
I'm using Yii version 1.1.13, and MySQL 5.1.something.
My problem isn't the SQL, but how to configure the Yii model classes to work in all cases. I can get the classes to work sometimes (simple eager loading) but not always (never for lazy loading).
First I will describe the database. Then the goal. Then I will include examples of code I've tried and why it failed.
Sorry for the length, this is complex and examples are necessary.
The database:
TABLE sites
columns:
id INT
name VARCHAR
type VARCHAR
rows:
id name type
-- ------- -----
1 Site A foo
2 Site B bar
3 Site C bar
TABLE field_options
columns:
id INT
field VARCHAR
option_value VARCHAR
option_label VARCHAR
rows:
id field option_value option_label
-- ----------- ------------- -------------
1 sites.type foo Foo Style Site
2 sites.type bar Bar-Like Site
3 sites.type bar Bar Site
So sites has an informal a reference to field_options where:
field_options.field = 'sites.type' and
field_options.option_value = sites.type
The goal:
The goal is for sites to look up the relevant field_options.option_label to go with its type value. If there happens to be more than one matching row, pick only one (any one, doesn't matter which).
Using SQL this is easy, I can do it 2 ways:
I can join using a subquery:
SELECT
sites.id,
f1.option_label AS type_label
FROM sites
LEFT JOIN field_options AS f1 ON f1.id = (
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
)
Or I can use a subquery as a column reference in the select clause:
SELECT
sites.id,
(
SELECT id FROM field_options
WHERE
field_options.field = 'sites.type'
AND field_options.option_value = sites.type
LIMIT 1
) AS type_label
FROM sites
Either way works great. So how do I model this in Yii??
What I've tried so far:
1. Use "on" array key in relation
I can get a simple eager lookup to work with this code:
class Sites extends CActiveRecord
{
...
public function relations()
{
return array(
'type_option' => array(
self::BELONGS_TO,
'FieldOptions', // that's the class for field_options
'', // no normal foreign key
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = t.type LIMIT 1)",
),
);
}
}
This works when I load a set of Sites objects and force it to eager load type_label, e.g. Sites::model()->with('type_label')->findByPk(1).
It does not work if type_label is lazy-loaded.
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // ERROR: column t.type doesn't exist
2. Force eager loading always
Building on #1 above, I tried forcing Yii to always to eager loading, never lazy loading:
class Sites extends CActiveRecord
{
public function relations()
{
....
}
public function defaultScope()
{
return array(
'with' => array( 'type_option' ),
);
}
}
Now everything always works when I load Sites, but it's no good because there are other models (not pictured here) that have relations that point to Sites, and those result in errors:
$site = Sites::model()->findByPk(1);
$label = $site->type_option->option_label; // works now
$other = OtherModel::model()->with('site_relation')->findByPk(1); // ERROR: column t.type doesn't exist, because 't' refers to OtherModel now
3. Make the reference to the base table somehow relative
If there was a way that I could refer to the base table, other than "t", that was guaranteed to point to the correct alias, that would work, e.g.
'on' => "type_option.id = (SELECT id FROM field_options WHERE field = 'sites.type' AND option_value = %%BASE_TABLE%%.type LIMIT 1)",
where %%BASE_TABLE%% always refers to the correct alias for table sites. But I know of no such token.
4. Add a true virtual database column
This way would be the best, if I could convince Yii that the table has an extra column, which should be loaded just like every other column, except the SQL is a subquery -- that would be awesome. But again, I don't see any way to mess with the column list, it's all done automatically.
So, after all that... does anyone have any ideas?
EDIT Mar 21/15: I just spent a long time investigating the possibility of subclassing parts of Yii to get the job done. No luck.
I tried creating a new type of relation based on BELONGS_TO (class CBelongsToRelation), to see if I could somehow add in context sensitivity so it could react differently depending on whether it was being lazy-loaded or not. But Yii isn't built that way. There is no place where I can hook in code during query buiding from inside a relation object. And there is also no way I can tell even what the base class is, relation objects have no link back to the parent model.
All of the code that assembles these queries for active records and their relations is locked up in a separate set of classes (CActiveFinder, CJoinQuery, etc.) that cannot be extended or replaced without replacing the entire AR system pretty much. So that's out.
I then tried to see if I can create "fake" database column entries that would actually be a subquery. Answer: no. I figured out how I could add additional columns to Yii's automatically generated schema data. But,
a) there's no way to define a column in such a way that it can be a derived value, Yii assumes it's a column name in way too many places for that; and
b) there also doesn't appear to be any way to avoid having it try to insert/update to those columns on save.
So it really is looking like Yii (1.x) just does not have any way to make this happen.
Limited solution provided by #eggyal in comments: #eggyal has a suggestion that will meet my needs. He suggests creating a MySQL view table to add extra columns for each label, using a subquery to look up the value. To allow editing, the view would have to be tied to a separate Yii class, so the downside is everywhere in my code I need to be aware of whether I'm loading a record for reading only (must use the view's class) or read/write (must use the base table's class, does not have the extra columns). That said, it is a workable solution for my particular case, maybe even the only solution -- although not an answer to this question as written, so I'm not going to put it in as an answer.
OK, after a lot of attempts, I have found a solution. Thanks to #eggyal for making me think about database views.
As a quick recap, my goal was:
link one Yii model (CActiveRecord) to another using a relation()
the table join is complex and could match more than one row
the relation must never join more than one row (i.e. LIMIT 1)
I got it to work by:
creating a view from the field_options base table, using SQL GROUP BY to eliminate duplicate rows
creating a separate Yii model (CActiveRecord class) for the view
using the new model/view for the relation(), not the original table
Even then there were some wrinkles (maybe a Yii bug?) I had to work around.
Here are all the details:
The SQL view:
CREATE VIEW field_options_distinct AS
SELECT
field,
option_value,
option_label
FROM
field_options
GROUP BY
field,
option_value
;
This view contains only the columns I care about, and only ever one row per field/option_value pair.
The Yii model class:
class FieldOptionsDistinct extends CActiveRecord
{
public function tableName()
{
return 'field_options_distinct'; // the view
}
/*
I found I needed the following to override Yii's default table data.
The view doesn't have a primary key, and that confused Yii's AR finding system
and resulted in a PHP "invalid foreach()" error.
So the code below works around it by diving into the Yii table metadata object
and manually setting the primary key column list.
*/
private $bMetaDataSet = FALSE;
public function getMetaData()
{
$oMetaData = parent::getMetaData();
if (!$this->bMetaDataSet) {
$oMetaData->tableSchema->primaryKey = array( 'field', 'option_value' );
$this->bMetaDataSet = TRUE;
}
return $oMetaData;
}
}
The Yii relation():
class Sites extends CActiveRecord
{
// ...
public function relations()
{
return (
'type_option' => array(
self::BELONGS_TO,
'FieldOptionsDistinct',
array(
'type' => 'option_value',
),
'on' => "type_option.field = 'sites.type'",
),
);
}
}
And all that does the trick. Easy, right?!?
If a table has defaults on certain fields and NULL is not allowed, one would expect the insert script to use those defaults, as MariaDB/MySQL usually does. For example, if the table products has an AI field "id", a required field "name" and two required fields "active" and "featured" which both default to 1, then the query
INSERT INTO products (name) VALUES ('someName');
automatically inserts 1 as the value of active and featured. However, when using Phalcon's models like so:
$product = new Products();
$product->setName('someName');
$product->save();
returns validation errors saying "active" and "featured" are required.
Is there a flag I should provide during model generation in order for Phalcon tools to harvest and input the defaults into Model classes, or another way to make Phalcon automatically use defaults if found? Best approach would be just ignoring the fields that weren't set, I reckon. Can I make the models do that?
You can use a raw database value to avoid that, in specific inserts:
<?php
use Phalcon\Db\RawValue;
$product = new Products();
$product->setName('someName');
$product->setType(new RawValue('default')); //use default here
$product->save();
Or, general before create/update for specific fields:
use Phalcon\Db\RawValue;
class Products extends Phalcon\Mvc\Model
{
public function beforeValidationOnCreate()
{
$this->type = new RawValue('default');
}
}
Or ignore these fields in every SQL INSERT generated:
use Phalcon\Db\RawValue;
class Products extends Phalcon\Mvc\Model
{
public function initialize()
{
$this->skipAttributesOnCreate(array('type'));
}
}
Although I find twistedxtra's answer fascinating from the aspect that Phalcon contains this wicked method to read the column default, I believe from a architectural point of view this might be the wrong approach as you rely on your database to define the defaults of the properties of your model.
I would set the default value when declaring the property and keep the logic in the application layer. But that's just me.
Use Like below
The skipAttributesOnCreate will make sure Phalcon does not attempt to put a a value in that column. The database will apply the default value.
public function initialize()
{
$this->setSource('table_name');
$this->skipAttributesOnCreate(['name_of_column']);
}
I'm currently modifying a component to include ACL support.
I would like to create the correct #_assets row for every category/item created.
What would the correct procedure be to respect hierarchy and recompute lft and rght values?
Example:
Component
Category (lft:0 rght:1)
Item (lft:2 rght:3)
Category (lft:4 rght:5)
Item (lft:6 rght:7)
New Item (lft:? rght:?)
Category (lft:8 rght:9)
Generally you don't write directly to #_assets normally you would add ACL support as shown in the tutorial and then as each item is saved the acl is updated.
You probably want to read this article as well on adding ACL rules to your component as well.
Finally if you're talking about processing existing records to add ACL then the most common approach is to process each record using your updated model (that now has ACL support) to re-save them. This will result in the ACL being applied properly not just updating the #_assets table.
if you want to add your component's item to the assets table:
I'll assume you have added the file access.xml w/ the sections component, categories, and item like so:
....
**<section name="item">
<action name="core.delete" title="JACTION_DELETE" description="COM_CONTENT_ACCESS_DELETE_DESC" />
<action name="core.edit" title="JACTION_EDIT" description="COM_CONTENT_ACCESS_EDIT_DESC" />
<action name="core.edit.state" title="JACTION_EDITSTATE" description="COM_CONTENT_ACCESS_EDITSTATE_DESC" />
</section>**
....
Here I will refer to the section item as the component's item (i.e. an article, a banner, a weblink, etc.)
Your component item table must have the column asset_id, because the existence of this field triggers the saving of this item to the #__assets table
Jtable class
... __construct()
{
...
if (property_exists($this, 'asset_id'))
{
$this->_trackAssets = true;
}
...
}
If you are following joomla's component development and file structure guidelines, then your ComponentTableItem store() method would call parent::store(); It is in the parent class, JTable->store(), that the insert to the the assets table occurs. If the item record was successfully saved to its table, then the class continues by checking for the existence of the asset_id field.
JTable->store() checks
// If the table is not set to track assets return true.
if (!$this->_trackAssets)
{
return true;
}
If this exists, then code continues to execute getting the asset Parent id, the asset name to use when adding the record to the table, and the title of this item.
Take a look at JOOMLA_ROOT/libraries/joomla/database/table.php and search for method store(), code excerpt:
$parentId = $this->_getAssetParentId();
$name = $this->_getAssetName();
$title = $this->_getAssetTitle();
If you want to control how the asset name gets recorded, for example, com_yourComponent.item.12 or com_yourComponent.event.14, or com_yourComponent.YOURITEM.ID, (look at your joomla database, table #__assets to better understand what I am referring to, i.e. the structure of the assets' names) you can, or might need to, rewrite the methods _getAssetParentID(), _getAssetName(), and _getAssetTitle() in your Component's table class.
Assuming a new item, once recorded to the #__assets table, the method UPDATES the recently added item record with the corresponding asset id, i.e. #assets.id = #_yourComponent_item.asset_id. From JTable->store() continued:
if (empty($this->asset_id))
{
// Update the asset_id field in this table.
$this->asset_id = (int) $asset->id;
$query = $this->_db->getQuery(true);
$query->update($this->_db->quoteName($this->_tbl));
$query->set('asset_id = ' . (int) $this->asset_id);
$query->where($this->_db->quoteName($k) . ' = ' . (int) $this->$k);
$this->_db->setQuery($query);
if (!$this->_db->query())
{
$e = new JException(JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED_UPDATE_ASSET_ID', $this->_db->getErrorMsg()));
$this->setError($e);
return false;
}
}
Of course, as cppl mentioned above, you must have implemented the ACL rules to your component's item form and binded these before saving.
I strongly suggest you read the documentation cppl redirects you to.
I have a nasty problem. I want to get rid of a certain database field, but I'm not sure in which bits of code it's called. Is there a way to find out where this field is used/called from (except for text searching the code; this is fairly useless seeing as how the field is named 'email')?
Cheers
I would first text search the files for the table name, then only search the tables that contain the table name for the field name.
I wrote a program to do this for my own purposes. It builds an in-memory listing of tables and fields and relates the tables to the fields. Then it loops through tables, searching for the code files that contain the table names, and then searches those files for the fields in the tables found. I'd recommend a similar methodology in your case.
setting mysql to log all queries for some time might help. the queries will give you the tip where to look
brute force - set up a test instance - remove the column - and excercise your test suite.
create a before insert trigger on that table that monitors the insertion on that column.
at the same time create another table called monitor with only one column email
make that table insert the value of NEW.email field into monitor.email as well as in real table.
so you can run your application and check for the existence of any non-null value in monitor table
You should do this in PHP i would expect
For example:
<?php
class Query
{
var $command;
var $resource;
function __construct($sql_command = '')
{
$this->command = $sql_command;
}
public function setResource($resource)
{
$this->resource = $resource;
}
}
//then you would have some kind of database class, but here we would modify the query method.
class Database
{
function query(Query $query)
{
$resource = mysql_query($query->command);
$query->setResource($resource);
//Then you can send the class to the monitor
QueryMonitor::Monitor($query);
}
}
abstract class QueryMonitor
{
public static Monitor(Query $query)
{
//here you use $query->resource to do monitoring of queryies
//You can also parse the query and gather what query type it was:-
//Select or Delete, you can also mark what tables were in the Query
//Even meta data so
$total_found = mysql_num_rows($query->resource);
$field_table = mysql_field_table ($query->resource);
//Just an example..
}
}
?>
Obviously it would be more advanced than that but you can set up a system to monitor every query and every queries meta data in a log file or w.e