You can't have two classes in the same file. Instead, you can write it like this:
global class batchELQA_Delete implements Database.Batchable<SObject>, Schedulable {
global Database.QueryLocator start(Database.BatchableContext BC) {
String query = 'Select Id FROM ELQA_Marketing_Activity__c WHERE CreatedDate < LAST_N_DAYS:180';
return Database.getQueryLocator(query);
}
global void execute(Database.BatchableContext BC, List<SObject> scope) {
delete scope;
DataBase.emptyRecycleBin(scope);
}
global void finish(Database.BatchableContext BC){
}
global void execute(SchedulableContext sc) {
ID BatchId = Database.executeBatch(new batchELQA_Delete(), 2000);
}
Public static void SchedulerMethod() {
string con_exp= '0 0 1 * * ?';
System.schedule('batchELQA_Delete', con_exp, new batchELQA_Delete());
}
}
There's really no reason that you need to do all that fancy footwork. You can just trust the system to tell you what you need to know. Here's an implementation that I whipped up for you:
public class Utils {
static Boolean validId(String recordIdString) {
try {
Id recordId = (Id)recordIdString;
String sobjectName = String.valueOf(recordId.getSObjectType());
return Database.countQuery('SELECT COUNT() FROM '+sobjectName+' WHERE Id = :recordId') > 0;
} catch(Exception e) {
return false;
}
}
}
I've also posted this as a gist.
The try-catch block will catch the TypeException if it's not a valid Id (from the cast), or a NullPointerException if it's a null Id, and possibly a QueryException if the Id points to an object that can't be queried (rare, but they are there).
I'm pretty sure that even the automated scanner won't give you a red flag for this code, but even if it did, a simple review should clearly identify that no injection is possible as long as it's used in the appropriate sharing context; user-facing code should be "with sharing" to prevent leaking Id values they should't know about.
You can actually do this without a try-catch as well:
static Boolean validId(String recordIdString) {
return recordIdString instanceOf Id &&
Id.valueOf(recordIdString).getSObjectType().getDescribe().isQueryable() &&
0 < Database.countQuery(
'SELECT COUNT() FROM '+
String.valueOf(Id.valueOf(recordIdString).getSObjectType())+
' WHERE Id = :recordIdString');
}
}
I find this to be a little less readable, but is Exception-free and probably better for performance.
Note: Id.valueOf is broken in older versions of the API, so make sure you're using a recent version of the API for your class (e.g. version 37.0).
Best Answer
You're missing a space between the last field and from:
As a side note, if you're not using dynamic features, like variable fields, you should use an inline query: